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..20e891d5fd3
--- /dev/null
+++ b/flow-server/src/main/java/com/vaadin/flow/server/VaadinFilter.java
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+import java.io.Serializable;
+
+/**
+ * Used to provide an around-like aspect option around request processing.
+ *
+ * @author Marcin Grzejszczak
+ * @since 24.2
+ */
+public interface VaadinFilter extends Serializable {
+
+ /**
+ * 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..97e37c3c0b5 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,15 @@ private void handleExceptionDuringRequest(VaadinRequest request,
vaadinSession.lock();
}
try {
+ try {
+ vaadinFilters.forEach(vaadinFilter -> vaadinFilter.handleException(request,
+ response, vaadinSession, t));
+ } catch (Exception ex) {
+ // An exception occurred while handling an exception. Log
+ // it and continue handling only the original error.
+ getLogger().warn(
+ "Failed to handle an exception using filters", ex);
+ }
if (vaadinSession != null) {
vaadinSession.getErrorHandler().error(new ErrorEvent(t));
}
@@ -2374,6 +2356,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
@@ -2398,4 +2388,4 @@ private void doSetClassLoader() {
setDefaultClassLoader();
}
}
-}
+}
\ No newline at end of file
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..6de41dd7527 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.
@@ -589,9 +584,9 @@ static URL getApplicationUrl(HttpServletRequest request)
final URL reqURL = new URL((request.isSecure() ? "https://" : "http://")
+ request.getServerName()
+ ((request.isSecure() && request.getServerPort() == 443)
- || (!request.isSecure()
- && request.getServerPort() == 80) ? ""
- : ":" + request.getServerPort())
+ || (!request.isSecure()
+ && request.getServerPort() == 80) ? ""
+ : ":" + request.getServerPort())
+ request.getRequestURI());
String servletPath;
if (request
@@ -601,7 +596,7 @@ static URL getApplicationUrl(HttpServletRequest request)
.getAttribute("jakarta.servlet.include.context_path")
.toString()
+ request.getAttribute(
- "jakarta.servlet.include.servlet_path");
+ "jakarta.servlet.include.servlet_path");
} else {
servletPath = request.getContextPath() + request.getServletPath();
@@ -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.
*
@@ -656,4 +659,4 @@ public static String getFrontendMapping() {
return frontendMapping;
}
-}
+}
\ No newline at end of file
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..a4a8378b0bc 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,9 @@ public VaadinServletService(VaadinServlet servlet,
DeploymentConfiguration deploymentConfiguration) {
super(deploymentConfiguration);
this.servlet = servlet;
+ if (servlet != null) {
+ setVaadinFilters(servlet.getVaadinFilters());
+ }
}
/**
@@ -157,7 +158,7 @@ private boolean isOtherRequest(VaadinRequest request) {
return type == null
|| ApplicationConstants.REQUEST_TYPE_INIT.equals(type)
|| ApplicationConstants.REQUEST_TYPE_WEBCOMPONENT_RESYNC
- .equals(type);
+ .equals(type);
}
public static HttpServletRequest getCurrentServletRequest() {
@@ -306,4 +307,4 @@ protected VaadinContext constructVaadinContext() {
protected void setDefaultClassLoader() {
setClassLoader(getServlet().getServletContext().getClassLoader());
}
-}
+}
\ No newline at end of file
diff --git a/flow-server/src/test/java/com/vaadin/flow/server/MockServletServiceSessionSetup.java b/flow-server/src/test/java/com/vaadin/flow/server/MockServletServiceSessionSetup.java
index afdb60b0d24..cee88c67c0f 100644
--- a/flow-server/src/test/java/com/vaadin/flow/server/MockServletServiceSessionSetup.java
+++ b/flow-server/src/test/java/com/vaadin/flow/server/MockServletServiceSessionSetup.java
@@ -1,5 +1,19 @@
package com.vaadin.flow.server;
+import com.vaadin.flow.di.Instantiator;
+import com.vaadin.flow.di.Lookup;
+import com.vaadin.flow.di.ResourceProvider;
+import com.vaadin.flow.function.DeploymentConfiguration;
+import com.vaadin.flow.internal.CurrentInstance;
+import com.vaadin.flow.internal.ResponseWriterTest.CapturingServletOutputStream;
+import com.vaadin.flow.router.QueryParameters;
+import com.vaadin.flow.router.Router;
+import com.vaadin.flow.router.TestRouteRegistry;
+import com.vaadin.flow.server.AppShellRegistry.AppShellRegistryWrapper;
+import com.vaadin.flow.server.communication.IndexHtmlRequestListener;
+import com.vaadin.flow.server.communication.IndexHtmlResponse;
+import com.vaadin.flow.shared.ApplicationConstants;
+import com.vaadin.tests.util.MockDeploymentConfiguration;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
@@ -7,6 +21,9 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -23,25 +40,6 @@
import java.util.concurrent.LinkedBlockingDeque;
import java.util.function.Supplier;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import com.vaadin.flow.di.Instantiator;
-import com.vaadin.flow.di.Lookup;
-import com.vaadin.flow.di.ResourceProvider;
-import com.vaadin.flow.function.DeploymentConfiguration;
-import com.vaadin.flow.internal.CurrentInstance;
-import com.vaadin.flow.internal.ResponseWriterTest.CapturingServletOutputStream;
-import com.vaadin.flow.router.QueryParameters;
-import com.vaadin.flow.router.Router;
-import com.vaadin.flow.router.TestRouteRegistry;
-import com.vaadin.flow.server.AppShellRegistry.AppShellRegistryWrapper;
-import com.vaadin.flow.server.communication.IndexHtmlRequestListener;
-import com.vaadin.flow.server.communication.IndexHtmlResponse;
-import com.vaadin.flow.shared.ApplicationConstants;
-import com.vaadin.tests.util.MockDeploymentConfiguration;
-
public class MockServletServiceSessionSetup {
public class TestVaadinServletService extends VaadinServletService {
@@ -51,6 +49,7 @@ public class TestVaadinServletService extends VaadinServletService {
private Router router;
private List indexHtmlRequestListeners = new ArrayList<>();
private VaadinContext context;
+ private List handlers;
public TestVaadinServletService(TestVaadinServlet testVaadinServlet,
DeploymentConfiguration deploymentConfiguration) {
@@ -74,6 +73,18 @@ public void setRouteRegistry(TestRouteRegistry routeRegistry) {
this.routeRegistry = routeRegistry;
}
+ @Override
+ protected List createRequestHandlers()
+ throws ServiceException {
+ List requestHandlers = super.createRequestHandlers();
+ handlers = requestHandlers;
+ return requestHandlers;
+ }
+
+ public List getRequestHandlers() {
+ return handlers;
+ }
+
@Override
protected RouteRegistry getRouteRegistry() {
if (routeRegistry != null) {
diff --git a/flow-server/src/test/java/com/vaadin/flow/server/VaadinServletServiceTest.java b/flow-server/src/test/java/com/vaadin/flow/server/VaadinServletServiceTest.java
index eaf6ae4e72c..857114180fd 100644
--- a/flow-server/src/test/java/com/vaadin/flow/server/VaadinServletServiceTest.java
+++ b/flow-server/src/test/java/com/vaadin/flow/server/VaadinServletServiceTest.java
@@ -1,22 +1,25 @@
package com.vaadin.flow.server;
+import com.vaadin.flow.di.Instantiator;
+import com.vaadin.flow.server.MockServletServiceSessionSetup.TestVaadinServletService;
+import com.vaadin.flow.theme.AbstractTheme;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Collections;
-import java.util.List;
-
+import jakarta.servlet.http.HttpSession;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
-import com.vaadin.flow.di.Instantiator;
-import com.vaadin.flow.server.MockServletServiceSessionSetup.TestVaadinServletService;
-import com.vaadin.flow.theme.AbstractTheme;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
/**
* Test class for testing es6 resolution by browser capability. This is valid
@@ -220,6 +223,9 @@ private HttpServletRequest createRequest(String base, String contextPath,
String servletPath, String pathInfo) throws MalformedURLException {
URL url = new URL(base + contextPath + pathInfo);
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ Map attributes = new HashMap<>();
+ attributes.put("null.lock", new ReentrantLock()); // for session
+ attributes.put("requestStartTime", System.currentTimeMillis()); // for request end
Mockito.when(request.isSecure())
.thenReturn(url.getProtocol().equalsIgnoreCase("https"));
Mockito.when(request.getServerName()).thenReturn(url.getHost());
@@ -228,8 +234,85 @@ private HttpServletRequest createRequest(String base, String contextPath,
Mockito.when(request.getContextPath()).thenReturn(contextPath);
Mockito.when(request.getPathInfo()).thenReturn(pathInfo);
Mockito.when(request.getServletPath()).thenReturn(servletPath);
-
+ HttpSession session = Mockito.mock(HttpSession.class);
+ Mockito.when(request.getSession()).thenReturn(session);
+ Mockito.when(request.getSession(Mockito.anyBoolean())).thenReturn(session);
+ stubSessionAttributes(session, attributes);
+ stubAttributes(request, attributes);
return request;
}
+ private static void stubSessionAttributes(HttpSession session,
+ Map attributes) {
+ Mockito.when(
+ session.getAttribute(Mockito.anyString())).thenAnswer(invocation -> attributes.get(invocation.getArgument(0)));
+ Mockito.doAnswer(invocation -> attributes.put(invocation.getArgument(0), invocation.getArgument(1))).when(
+ session).setAttribute(Mockito.anyString(), Mockito.anyString());
+ }
+
+ private static void stubAttributes(HttpServletRequest request,
+ Map attributes) {
+ Mockito.when(
+ request.getAttribute(Mockito.anyString())).thenAnswer(invocation -> attributes.get(invocation.getArgument(0)));
+ Mockito.doAnswer(invocation -> attributes.put(invocation.getArgument(0), invocation.getArgument(1))).when(
+ request).setAttribute(Mockito.anyString(), Mockito.anyString());
+ }
+
+ @Test
+ public void filtersAreCalledWhenHandlingARequest()
+ throws MalformedURLException {
+ VaadinRequest request = servlet.createVaadinRequest(createRequest("http://dummy.host:8080/", "/contextpath", "/servlet", "/"));
+ VaadinResponse response = Mockito.mock(VaadinResponse.class);
+ service.setVaadinFilters(Collections.singletonList(new MyFilter()));
+ service.getRequestHandlers().clear();
+ service.getRequestHandlers().add(new ExceptionThrowingRequestHandler());
+
+ try {
+ service.handleRequest(request, response);
+ } catch (ServiceException ex) {
+ Assert.assertTrue("The exception was the one coming from RequestHandler", ex.getMessage().contains("BOOM!"));
+ }
+
+ Assert.assertEquals("Filter was called on request start", "true", request.getAttribute("started"));
+ Assert.assertEquals("Filter was called on exception handling", "true", request.getAttribute("exception handled"));
+ Assert.assertEquals("Filter was called in the finally block", "true", request.getAttribute("ended"));
+ }
+
+ static class ExceptionThrowingRequestHandler implements RequestHandler {
+
+ @Override
+ public boolean handleRequest(VaadinSession session,
+ VaadinRequest request, VaadinResponse response)
+ throws IOException {
+ throw new IllegalStateException("BOOM!");
+ }
+ }
+
+ static class MyFilter implements VaadinFilter {
+
+ @Override
+ public void requestStart(VaadinRequest request,
+ VaadinResponse response) {
+ request.setAttribute("started", "true");
+ // An exception thrown here will not be caught by other methods of the filter!
+ }
+
+ @Override
+ public void handleException(VaadinRequest request,
+ VaadinResponse response, VaadinSession vaadinSession,
+ Exception t) {
+ if (t instanceof IllegalStateException ex) {
+ Assert.assertEquals("BOOM!", ex.getMessage());
+ request.setAttribute("exception handled", "true");
+ return;
+ }
+ throw new AssertionError("Invalid exception thrown. Wanted got <" + t.getClass() + ">", t);
+ }
+
+ @Override
+ public void requestEnd(VaadinRequest request, VaadinResponse response,
+ VaadinSession session) {
+ request.setAttribute("ended", "true");
+ }
+ }
}
diff --git a/pom.xml b/pom.xml
index b0a1004428f..924e299af17 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));
+ }
+ }
}
diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/SpringBootAutoConfigurationRootMappedTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/SpringBootAutoConfigurationRootMappedTest.java
index 3eaebf401ad..b9005fd5512 100644
--- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/SpringBootAutoConfigurationRootMappedTest.java
+++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/SpringBootAutoConfigurationRootMappedTest.java
@@ -1,19 +1,23 @@
package com.vaadin.flow.spring;
-import java.util.Set;
-
+import com.vaadin.flow.server.*;
+import jakarta.servlet.ServletException;
import org.atmosphere.cpr.ApplicationConfig;
import org.junit.Assert;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
+import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
+import org.springframework.test.context.ContextConfiguration;
-import com.vaadin.flow.server.Constants;
+import java.util.Set;
@SpringBootTest(classes = SpringBootAutoConfiguration.class)
-// @ContextConfiguration(SpringBootAutoConfiguration.class)
+@ContextConfiguration(classes = SpringBootAutoConfigurationRootMappedTest.TestConfig.class)
public class SpringBootAutoConfigurationRootMappedTest {
// private SpringBootAutoConfiguration autoConfiguration;
@@ -21,11 +25,12 @@ public class SpringBootAutoConfigurationRootMappedTest {
private ServletRegistrationBean servletRegistrationBean;
@Autowired
private Environment environment;
+ @Autowired
+ private MyFilter myFilter;
@Test
public void urlMappingPassedToAtmosphere() {
- Assert.assertTrue(RootMappedCondition
- .isRootMapping(RootMappedCondition.getUrlMapping(environment)));
+ Assert.assertTrue(RootMappedCondition.isRootMapping(RootMappedCondition.getUrlMapping(environment)));
Assert.assertEquals(
Set.of(VaadinServletConfiguration.VAADIN_SERVLET_MAPPING),
servletRegistrationBean.getUrlMappings());
@@ -33,4 +38,42 @@ public void urlMappingPassedToAtmosphere() {
servletRegistrationBean.getInitParameters()
.get(ApplicationConfig.JSR356_MAPPING_PATH));
}
+
+ @Test
+ public void filtersAreRegisteredOnTheServlet() throws ServletException {
+ SpringServlet servlet = servletRegistrationBean.getServlet();
+
+ Assertions.assertEquals(1, servlet.getVaadinFilters().size(),
+ "There should be 1 filter");
+ Assertions.assertInstanceOf(MyFilter.class, servlet.getVaadinFilters().get(0),
+ "MyFilter should be registered");
+ }
+
+ @TestConfiguration(proxyBeanMethods = false)
+ static class TestConfig {
+ @Bean
+ MyFilter myFilter() {
+ return new MyFilter();
+ }
+ }
+
+ static class MyFilter implements VaadinFilter {
+
+ @Override
+ public void requestStart(VaadinRequest request,
+ VaadinResponse response) {
+
+ }
+
+ @Override
+ public void handleException(VaadinRequest request,
+ VaadinResponse response, VaadinSession vaadinSession,
+ Exception t) {
+ }
+
+ @Override
+ public void requestEnd(VaadinRequest request, VaadinResponse response,
+ VaadinSession session) {
+ }
+ }
}