Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adds a VaadinRequestInterceptor abstraction #17502

Merged
merged 10 commits into from
Sep 26, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
*/
package com.vaadin.flow.server;

import com.vaadin.flow.server.communication.IndexHtmlRequestListener;

import java.util.ArrayList;
import java.util.EventObject;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

import com.vaadin.flow.server.communication.IndexHtmlRequestListener;

/**
* Event fired to {@link VaadinServiceInitListener} when a {@link VaadinService}
* is being initialized.
Expand All @@ -38,6 +38,7 @@ public class ServiceInitEvent extends EventObject {
private List<RequestHandler> addedRequestHandlers = new ArrayList<>();
private List<IndexHtmlRequestListener> addedIndexHtmlRequestListeners = new ArrayList<>();
private List<DependencyFilter> addedDependencyFilters = new ArrayList<>();
private List<VaadinRequestInterceptor> addedVaadinRequestInterceptors = new ArrayList<>();

/**
* Creates a new service init event for a given {@link VaadinService} and
Expand Down Expand Up @@ -92,6 +93,20 @@ public void addDependencyFilter(DependencyFilter dependencyFilter) {
addedDependencyFilters.add(dependencyFilter);
}

/**
* Adds a new request interceptor that will be used by this service.
*
* @param vaadinRequestInterceptor
* the request interceptor to add, not <code>null</code>
*/
public void addVaadinRequestInterceptor(
VaadinRequestInterceptor vaadinRequestInterceptor) {
Objects.requireNonNull(vaadinRequestInterceptor,
"Request Interceptor cannot be null");

addedVaadinRequestInterceptors.add(vaadinRequestInterceptor);
}

/**
* Gets a stream of all custom request handlers that have been added for the
* service.
Expand Down Expand Up @@ -122,6 +137,16 @@ public Stream<DependencyFilter> getAddedDependencyFilters() {
return addedDependencyFilters.stream();
}

/**
* Gets a stream of all Vaadin request interceptors that have been added for
* the service.
*
* @return the stream of added request interceptors
*/
public Stream<VaadinRequestInterceptor> getAddedVaadinRequestInterceptor() {
return addedVaadinRequestInterceptors.stream();
}

@Override
public VaadinService getSource() {
return (VaadinService) super.getSource();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* 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 VaadinRequestInterceptor 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);
}
132 changes: 96 additions & 36 deletions flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -79,11 +44,44 @@
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.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 static java.nio.charset.StandardCharsets.UTF_8;

Expand Down Expand Up @@ -181,6 +179,8 @@ public abstract class VaadinService implements Serializable {

private VaadinContext vaadinContext;

private Iterable<VaadinRequestInterceptor> vaadinRequestInterceptors;

/**
* Creates a new vaadin service based on a deployment configuration.
*
Expand Down Expand Up @@ -221,6 +221,11 @@ public void init() throws ServiceException {

List<RequestHandler> handlers = createRequestHandlers();

// If the user has already provided interceptors we will add them to the
// list
// and append ones from the ServiceInitEvent
List<VaadinRequestInterceptor> requestInterceptors = createVaadinRequestInterceptors();

ServiceInitEvent event = new ServiceInitEvent(this);

// allow service init listeners and DI to use thread local access to
Expand All @@ -235,6 +240,14 @@ public void init() throws ServiceException {

requestHandlers = Collections.unmodifiableCollection(handlers);

event.getAddedVaadinRequestInterceptor()
.forEach(requestInterceptors::add);

Collections.reverse(requestInterceptors);

vaadinRequestInterceptors = Collections
.unmodifiableCollection(requestInterceptors);

dependencyFilters = Collections.unmodifiableCollection(instantiator
.getDependencyFilters(event.getAddedDependencyFilters())
.collect(Collectors.toList()));
Expand Down Expand Up @@ -323,6 +336,22 @@ protected List<RequestHandler> createRequestHandlers()
return handlers;
}

/**
* Called during initialization to add the request handlers for the service.
* Note that the returned list will be reversed so the last interceptor will
* be called first. This enables overriding this method and using add on the
* returned list to add a custom request interceptors which overrides any
* predefined handler.
*
* @return The list of request handlers used by this service.
* @throws ServiceException
* if a problem occurs when creating the request interceptors
*/
protected List<VaadinRequestInterceptor> createVaadinRequestInterceptors()
throws ServiceException {
return new ArrayList<>();
}

/**
* Creates an instantiator to use with this service.
* <p>
Expand Down Expand Up @@ -1433,6 +1462,9 @@ public void requestStart(VaadinRequest request, VaadinResponse response) {
}
setCurrentInstances(request, response);
request.setAttribute(REQUEST_START_TIME_ATTRIBUTE, System.nanoTime());
vaadinRequestInterceptors
.forEach(requestInterceptor -> requestInterceptor
.requestStart(request, response));
}

/**
Expand All @@ -1449,6 +1481,9 @@ public void requestStart(VaadinRequest request, VaadinResponse response) {
*/
public void requestEnd(VaadinRequest request, VaadinResponse response,
VaadinSession session) {
vaadinRequestInterceptors
.forEach(requestInterceptor -> requestInterceptor
.requestEnd(request, response, session));
if (session != null) {
assert VaadinSession.getCurrent() == session;
session.lock();
Expand Down Expand Up @@ -1476,6 +1511,19 @@ public Iterable<RequestHandler> getRequestHandlers() {
return requestHandlers;
}

/**
* Returns the request interceptors that are registered with this service.
* The iteration order of the returned collection is the same as the order
* in which the request handlers will be invoked when a request is handled.
*
* @return a collection of request interceptors in the order they are
* invoked
* @see #createVaadinRequestInterceptors()
*/
public Iterable<VaadinRequestInterceptor> getVaadinRequestInterceptors() {
return vaadinRequestInterceptors;
}

/**
* Gets the filters which all resource dependencies are passed through
* before being sent to the client for loading.
Expand Down Expand Up @@ -1544,6 +1592,18 @@ private void handleExceptionDuringRequest(VaadinRequest request,
vaadinSession.lock();
}
try {
try {
vaadinRequestInterceptors
Copy link
Contributor

@mshabarov mshabarov Sep 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleExceptionDuringRequest doesn't actually handle all the errors during request processing.
It catches internal errors, but errors that might happen inside lambdas provided by developers (mainly UI updates, for instance, if you pass an UI update into beforeClientResponse) are processed by ErrorHandler, so that the exception is not thrown up to the invocation hierarchy, but handled by this error handler. See https://vaadin.com/docs/latest/advanced/custom-error-handler.

This might be a default error handler or a custom error handler, which, for example shows the notification with error message or changes component's styles.

Also, if you search in the project by string .getErrorHandler().error(, you will find a few entries in the core code that catch exceptions and delegate them to the error handler.

Thus, to cover also the exceptions handled by ErrorHandler objects, we may add a ErrorHandlerWrapper that takes ErrorHandler in VaadinSession::setErrorHandler and decorates it with calls to request interceptors.

.forEach(requestInterceptor -> requestInterceptor
.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 request interceptors",
ex);
}
if (vaadinSession != null) {
vaadinSession.getErrorHandler().error(new ErrorEvent(t));
}
Expand Down
Loading