001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.server.handler;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.List;
022
023import reactor.core.publisher.Mono;
024
025import org.springframework.http.HttpMethod;
026import org.springframework.http.server.reactive.ServerHttpRequest;
027import org.springframework.util.StringUtils;
028import org.springframework.web.server.ServerWebExchange;
029import org.springframework.web.server.WebExceptionHandler;
030import org.springframework.web.server.WebHandler;
031
032/**
033 * WebHandler decorator that invokes one or more {@link WebExceptionHandler WebExceptionHandlers}
034 * after the delegate {@link WebHandler}.
035 *
036 * @author Rossen Stoyanchev
037 * @since 5.0
038 */
039public class ExceptionHandlingWebHandler extends WebHandlerDecorator {
040
041        private final List<WebExceptionHandler> exceptionHandlers;
042
043
044        /**
045         * Create an {@code ExceptionHandlingWebHandler} for the given delegate.
046         * @param delegate the WebHandler delegate
047         * @param handlers the WebExceptionHandlers to apply
048         */
049        public ExceptionHandlingWebHandler(WebHandler delegate, List<WebExceptionHandler> handlers) {
050                super(delegate);
051                List<WebExceptionHandler> handlersToUse = new ArrayList<>();
052                handlersToUse.add(new CheckpointInsertingHandler());
053                handlersToUse.addAll(handlers);
054                this.exceptionHandlers = Collections.unmodifiableList(handlersToUse);
055        }
056
057
058        /**
059         * Return a read-only list of the configured exception handlers.
060         */
061        public List<WebExceptionHandler> getExceptionHandlers() {
062                return this.exceptionHandlers;
063        }
064
065
066        @Override
067        public Mono<Void> handle(ServerWebExchange exchange) {
068                Mono<Void> completion;
069                try {
070                        completion = super.handle(exchange);
071                }
072                catch (Throwable ex) {
073                        completion = Mono.error(ex);
074                }
075
076                for (WebExceptionHandler handler : this.exceptionHandlers) {
077                        completion = completion.onErrorResume(ex -> handler.handle(exchange, ex));
078                }
079                return completion;
080        }
081
082
083        /**
084         * WebExceptionHandler to insert a checkpoint with current URL information.
085         * Must be the first in order to ensure we catch the error signal before
086         * the exception is handled and e.g. turned into an error response.
087         * @since 5.2
088         */
089        private static class CheckpointInsertingHandler implements WebExceptionHandler {
090
091                @Override
092                public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
093                        ServerHttpRequest request = exchange.getRequest();
094                        String rawQuery = request.getURI().getRawQuery();
095                        String query = StringUtils.hasText(rawQuery) ? "?" + rawQuery : "";
096                        HttpMethod httpMethod = request.getMethod();
097                        String description = "HTTP " + httpMethod + " \"" + request.getPath() + query + "\"";
098                        return Mono.<Void>error(ex).checkpoint(description + " [ExceptionHandlingWebHandler]");
099                }
100        }
101
102}