diff --git a/flow-server/pom.xml b/flow-server/pom.xml
index 5598dd73b79..d3cf19d24b0 100644
--- a/flow-server/pom.xml
+++ b/flow-server/pom.xml
@@ -135,6 +135,20 @@
commons-compress
1.23.0
+
+
+ io.micrometer
+ micrometer-observation
+ ${micrometer.version}
+ true
+
+
+ io.micrometer
+ micrometer-jakarta
+ ${micrometer.version}
+ true
+
+
com.vaadin
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/ObservedVaadinFilter.java b/flow-server/src/main/java/com/vaadin/flow/server/ObservedVaadinFilter.java
new file mode 100644
index 00000000000..1e031508930
--- /dev/null
+++ b/flow-server/src/main/java/com/vaadin/flow/server/ObservedVaadinFilter.java
@@ -0,0 +1,65 @@
+package com.vaadin.flow.server;
+
+import io.micrometer.common.lang.Nullable;
+import io.micrometer.jakarta.instrument.binder.http.DefaultHttpJakartaServerServletRequestObservationConvention;
+import io.micrometer.jakarta.instrument.binder.http.HttpJakartaServerServletRequestObservationContext;
+import io.micrometer.jakarta.instrument.binder.http.HttpJakartaServerServletRequestObservationConvention;
+import io.micrometer.jakarta.instrument.binder.http.JakartaHttpObservationDocumentation;
+import io.micrometer.observation.Observation;
+import io.micrometer.observation.ObservationRegistry;
+import jakarta.servlet.http.HttpServletResponse;
+
+/**
+ * Micrometer Observation {@link VaadinFilter} that will start
+ * observations around processing of a request.
+ *
+ * @author Marcin Grzejszczak
+ * @since 24.2
+ */
+public class ObservedVaadinFilter implements VaadinFilter {
+
+ private static final String SCOPE_ATTRIBUTE = ObservedVaadinFilter.class.getName() + ".scope";
+
+ private final ObservationRegistry observationRegistry;
+
+ private final HttpJakartaServerServletRequestObservationConvention convention;
+
+ public ObservedVaadinFilter(ObservationRegistry observationRegistry,
+ @Nullable HttpJakartaServerServletRequestObservationConvention convention) {
+ this.observationRegistry = observationRegistry;
+ this.convention = convention;
+ }
+
+ @Override
+ public void requestStart(VaadinRequest request, VaadinResponse response) {
+ if (request instanceof VaadinServletRequest servletRequest && response instanceof VaadinServletResponse servletResponse) {
+ HttpJakartaServerServletRequestObservationContext context = new HttpJakartaServerServletRequestObservationContext(servletRequest, servletResponse);
+ Observation observation = JakartaHttpObservationDocumentation.JAKARTA_SERVLET_SERVER_OBSERVATION.start(this.convention, DefaultHttpJakartaServerServletRequestObservationConvention.INSTANCE, () -> context, observationRegistry);
+ request.setAttribute(SCOPE_ATTRIBUTE, observation.openScope());
+ }
+ }
+
+ @Override
+ public void handleException(VaadinRequest request, VaadinResponse response, VaadinSession vaadinSession, Exception t) {
+ Observation.Scope scope = (Observation.Scope) request.getAttribute(SCOPE_ATTRIBUTE);
+ if (scope == null) {
+ return;
+ }
+ scope.getCurrentObservation().error(t);
+ }
+
+ @Override
+ public void requestEnd(VaadinRequest request, VaadinResponse response, VaadinSession session) {
+ Observation.Scope scope = (Observation.Scope) request.getAttribute(SCOPE_ATTRIBUTE);
+ if (scope == null) {
+ return;
+ }
+ scope.close();
+ Observation observation = scope.getCurrentObservation();
+ if (!observation.isNoop() && response instanceof HttpServletResponse httpServletResponse) {
+ HttpJakartaServerServletRequestObservationContext ctx = (HttpJakartaServerServletRequestObservationContext) observation.getContext();
+ ctx.setResponse(httpServletResponse);
+ }
+ observation.stop();
+ }
+}
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/VaadinFilter.java b/flow-server/src/main/java/com/vaadin/flow/server/VaadinFilter.java
new file mode 100644
index 00000000000..85abe419a7c
--- /dev/null
+++ b/flow-server/src/main/java/com/vaadin/flow/server/VaadinFilter.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2000-2023 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.flow.server;
+
+/**
+ * Used to provide an around-like aspect option around request processing.
+ *
+ * @author Marcin Grzejszczak
+ * @since 24.2
+ */
+public interface VaadinFilter {
+
+ /**
+ * Called when request is about to be processed.
+ * @param request request
+ * @param response response
+ */
+ void requestStart(VaadinRequest request, VaadinResponse response);
+
+ /**
+ * Called when an exception occurred
+ * @param request request
+ * @param response response
+ * @param vaadinSession session
+ * @param t exception
+ */
+ void handleException(VaadinRequest request,
+ VaadinResponse response, VaadinSession vaadinSession, Exception t);
+
+ /**
+ * Called in the finally block of processing a request. Will be called
+ * regardless of whether there was an exception or not.
+ * @param request request
+ * @param response response
+ * @param session session
+ */
+ void requestEnd(VaadinRequest request, VaadinResponse response,
+ VaadinSession session);
+}
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java b/flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java
index 98a9b134217..d2c60f93243 100644
--- a/flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java
+++ b/flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java
@@ -16,41 +16,6 @@
package com.vaadin.flow.server;
-import java.io.BufferedWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.io.Serializable;
-import java.lang.reflect.Constructor;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Optional;
-import java.util.ServiceLoader;
-import java.util.Set;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.stream.Collectors;
-import java.util.stream.StreamSupport;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import com.vaadin.flow.component.UI;
import com.vaadin.flow.di.DefaultInstantiator;
import com.vaadin.flow.di.Instantiator;
@@ -64,26 +29,30 @@
import com.vaadin.flow.router.RouteData;
import com.vaadin.flow.router.Router;
import com.vaadin.flow.server.HandlerHelper.RequestType;
-import com.vaadin.flow.server.communication.AtmospherePushConnection;
-import com.vaadin.flow.server.communication.HeartbeatHandler;
-import com.vaadin.flow.server.communication.IndexHtmlRequestListener;
-import com.vaadin.flow.server.communication.IndexHtmlResponse;
-import com.vaadin.flow.server.communication.JavaScriptBootstrapHandler;
-import com.vaadin.flow.server.communication.PwaHandler;
-import com.vaadin.flow.server.communication.SessionRequestHandler;
-import com.vaadin.flow.server.communication.StreamRequestHandler;
-import com.vaadin.flow.server.communication.UidlRequestHandler;
-import com.vaadin.flow.server.communication.WebComponentBootstrapHandler;
-import com.vaadin.flow.server.communication.WebComponentProvider;
+import com.vaadin.flow.server.communication.*;
import com.vaadin.flow.shared.ApplicationConstants;
import com.vaadin.flow.shared.JsonConstants;
import com.vaadin.flow.shared.Registration;
import com.vaadin.flow.shared.communication.PushMode;
-
import elemental.json.Json;
import elemental.json.JsonException;
import elemental.json.JsonObject;
import elemental.json.impl.JsonUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.lang.reflect.Constructor;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.concurrent.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -114,6 +83,8 @@ public abstract class VaadinService implements Serializable {
+ PushMode.class.getSimpleName() + "." + PushMode.DISABLED.name()
+ "." + SEPARATOR;
+ private List vaadinFilters = new ArrayList<>();
+
/**
* Attribute name for telling
* {@link VaadinSession#valueUnbound(jakarta.servlet.http.HttpSessionBindingEvent)}
@@ -1433,6 +1404,7 @@ public void requestStart(VaadinRequest request, VaadinResponse response) {
}
setCurrentInstances(request, response);
request.setAttribute(REQUEST_START_TIME_ATTRIBUTE, System.nanoTime());
+ vaadinFilters.forEach(vaadinFilter -> vaadinFilter.requestStart(request, response));
}
/**
@@ -1449,6 +1421,7 @@ public void requestStart(VaadinRequest request, VaadinResponse response) {
*/
public void requestEnd(VaadinRequest request, VaadinResponse response,
VaadinSession session) {
+ vaadinFilters.forEach(vaadinFilter -> vaadinFilter.requestEnd(request, response, session));
if (session != null) {
assert VaadinSession.getCurrent() == session;
session.lock();
@@ -1544,6 +1517,7 @@ private void handleExceptionDuringRequest(VaadinRequest request,
vaadinSession.lock();
}
try {
+ vaadinFilters.forEach(vaadinFilter -> vaadinFilter.handleException(request, response, vaadinSession, t));
if (vaadinSession != null) {
vaadinSession.getErrorHandler().error(new ErrorEvent(t));
}
@@ -2374,6 +2348,14 @@ public static String getCsrfTokenAttributeName() {
+ ApplicationConstants.CSRF_TOKEN;
}
+ public List getVaadinFilters() {
+ return vaadinFilters;
+ }
+
+ public void setVaadinFilters(List vaadinFilters) {
+ this.vaadinFilters = vaadinFilters;
+ }
+
private void doSetClassLoader() {
final String classLoaderName = getDeploymentConfiguration() == null
? null
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/VaadinServlet.java b/flow-server/src/main/java/com/vaadin/flow/server/VaadinServlet.java
index 28a4636baee..af64b7b570e 100644
--- a/flow-server/src/main/java/com/vaadin/flow/server/VaadinServlet.java
+++ b/flow-server/src/main/java/com/vaadin/flow/server/VaadinServlet.java
@@ -15,19 +15,6 @@
*/
package com.vaadin.flow.server;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import com.vaadin.flow.component.UI;
import com.vaadin.flow.di.Lookup;
import com.vaadin.flow.function.DeploymentConfiguration;
@@ -37,7 +24,6 @@
import com.vaadin.flow.server.HandlerHelper.RequestType;
import com.vaadin.flow.server.startup.ApplicationConfiguration;
import com.vaadin.flow.shared.JsonConstants;
-
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
@@ -45,6 +31,13 @@
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
/**
* The main servlet, which handles all incoming requests to the application.
@@ -72,6 +65,8 @@ public class VaadinServlet extends HttpServlet {
private static List whenFrontendMappingAvailable = new ArrayList<>();
+ private List vaadinFilters = new ArrayList<>();
+
/**
* Called by the servlet container to indicate to a servlet that the servlet
* is being placed into service.
@@ -647,6 +642,14 @@ private VaadinServletContext initializeContext() {
return vaadinServletContext;
}
+ public List getVaadinFilters() {
+ return vaadinFilters;
+ }
+
+ public void setVaadinFilters(List vaadinFilters) {
+ this.vaadinFilters = vaadinFilters;
+ }
+
/**
* For internal use only.
*
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/VaadinServletService.java b/flow-server/src/main/java/com/vaadin/flow/server/VaadinServletService.java
index 05ea8926f3d..7aefcfb2bd8 100644
--- a/flow-server/src/main/java/com/vaadin/flow/server/VaadinServletService.java
+++ b/flow-server/src/main/java/com/vaadin/flow/server/VaadinServletService.java
@@ -16,9 +16,19 @@
package com.vaadin.flow.server;
+import com.vaadin.flow.function.DeploymentConfiguration;
+import com.vaadin.flow.internal.DevModeHandler;
+import com.vaadin.flow.internal.DevModeHandlerManager;
+import com.vaadin.flow.server.communication.FaviconHandler;
+import com.vaadin.flow.server.communication.IndexHtmlRequestHandler;
+import com.vaadin.flow.server.communication.PushRequestHandler;
+import com.vaadin.flow.server.startup.ApplicationRouteRegistry;
+import com.vaadin.flow.shared.ApplicationConstants;
import jakarta.servlet.GenericServlet;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.net.MalformedURLException;
@@ -27,18 +37,6 @@
import java.util.Objects;
import java.util.Optional;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.vaadin.flow.function.DeploymentConfiguration;
-import com.vaadin.flow.internal.DevModeHandler;
-import com.vaadin.flow.internal.DevModeHandlerManager;
-import com.vaadin.flow.server.communication.FaviconHandler;
-import com.vaadin.flow.server.communication.IndexHtmlRequestHandler;
-import com.vaadin.flow.server.communication.PushRequestHandler;
-import com.vaadin.flow.server.startup.ApplicationRouteRegistry;
-import com.vaadin.flow.shared.ApplicationConstants;
-
/**
* A service implementation connected to a {@link VaadinServlet}.
*
@@ -66,6 +64,7 @@ public VaadinServletService(VaadinServlet servlet,
DeploymentConfiguration deploymentConfiguration) {
super(deploymentConfiguration);
this.servlet = servlet;
+ setVaadinFilters(servlet.getVaadinFilters());
}
/**
diff --git a/pom.xml b/pom.xml
index 37b4c9de49d..ad8b17a5ee3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -97,6 +97,8 @@
4.0.3
32.1.2-jre
2.1.1
+ 1.12.0-SNAPSHOT
+ 1.2.0-SNAPSHOT
1.5
diff --git a/vaadin-spring/pom.xml b/vaadin-spring/pom.xml
index 5e6ef829aac..2b8ef66d721 100644
--- a/vaadin-spring/pom.xml
+++ b/vaadin-spring/pom.xml
@@ -15,6 +15,10 @@
Provides Spring integration for Vaadin applications.
jar
+
+ 1.12.0-SNAPSHOT
+
+
com.vaadin
@@ -57,6 +61,11 @@
spring-boot-starter-web
provided
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+ true
+
org.springframework
@@ -103,6 +112,12 @@
${project.version}
true
+
+ io.micrometer
+ micrometer-jakarta
+ ${micrometer.version}
+ true
+
org.springframework
@@ -175,13 +190,13 @@
-
- org.springframework.boot
- spring-boot-dependencies
- ${spring.boot.version}
- pom
- import
-
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring.boot.version}
+ pom
+ import
+
diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/SpringBootAutoConfiguration.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/SpringBootAutoConfiguration.java
index 1c6322f2854..cdd8da25b6c 100644
--- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/SpringBootAutoConfiguration.java
+++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/SpringBootAutoConfiguration.java
@@ -15,12 +15,19 @@
*/
package com.vaadin.flow.spring;
-import java.util.HashMap;
-import java.util.Map;
-
+import com.vaadin.flow.server.Constants;
+import com.vaadin.flow.server.ObservedVaadinFilter;
+import com.vaadin.flow.server.VaadinFilter;
+import com.vaadin.flow.server.VaadinServlet;
+import com.vaadin.flow.spring.springnative.VaadinBeanFactoryInitializationAotProcessor;
+import io.micrometer.jakarta.instrument.binder.http.HttpJakartaServerServletRequestObservationConvention;
+import io.micrometer.observation.ObservationRegistry;
+import jakarta.servlet.MultipartConfigElement;
import org.atmosphere.cpr.ApplicationConfig;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
@@ -34,11 +41,9 @@
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
-import com.vaadin.flow.server.Constants;
-import com.vaadin.flow.server.VaadinServlet;
-import com.vaadin.flow.spring.springnative.VaadinBeanFactoryInitializationAotProcessor;
-
-import jakarta.servlet.MultipartConfigElement;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
/**
* Spring boot auto-configuration class for Flow.
@@ -85,6 +90,7 @@ public ServletContextInitializer contextInitializer() {
@Bean
public ServletRegistrationBean servletRegistrationBean(
ObjectProvider multipartConfig,
+ ObjectProvider> vaadinFilters,
VaadinConfigurationProperties configurationProperties) {
String mapping = configurationProperties.getUrlMapping();
Map initParameters = new HashMap<>();
@@ -102,8 +108,10 @@ public ServletRegistrationBean servletRegistrationBean(
initParameters.put(ApplicationConfig.JSR356_MAPPING_PATH, pushUrl);
+ SpringServlet springServlet = new SpringServlet(context, rootMapping);
+ vaadinFilters.ifAvailable(springServlet::setVaadinFilters);
ServletRegistrationBean registration = new ServletRegistrationBean<>(
- new SpringServlet(context, rootMapping), mapping);
+ springServlet, mapping);
registration.setInitParameters(initParameters);
registration
.setAsyncSupported(configurationProperties.isAsyncSupported());
@@ -129,4 +137,14 @@ public ServerEndpointExporter websocketEndpointDeployer() {
return new VaadinWebsocketEndpointExporter();
}
+ @Configuration(proxyBeanMethods = false)
+ @AutoConfigureAfter(ObservationAutoConfiguration.class)
+ @ConditionalOnClass(ObservationRegistry.class)
+ static class ObservabilityConfig {
+
+ @Bean
+ ObservedVaadinFilter observedVaadinFilter(ObservationRegistry observationRegistry, ObjectProvider convention) {
+ return new ObservedVaadinFilter(observationRegistry, convention.getIfAvailable(() -> null));
+ }
+ }
}