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 org.apache.commons.logging.Log; 020import org.apache.commons.logging.LogFactory; 021import reactor.core.publisher.Mono; 022 023import org.springframework.http.HttpStatus; 024import org.springframework.http.server.reactive.ServerHttpRequest; 025import org.springframework.http.server.reactive.ServerHttpResponse; 026import org.springframework.lang.Nullable; 027import org.springframework.web.server.ResponseStatusException; 028import org.springframework.web.server.ServerWebExchange; 029import org.springframework.web.server.WebExceptionHandler; 030 031/** 032 * Handle {@link ResponseStatusException} by setting the response status. 033 * 034 * <p>By default exception stack traces are not shown for successfully resolved 035 * exceptions. Use {@link #setWarnLogCategory(String)} to enable logging with 036 * stack traces. 037 * 038 * @author Rossen Stoyanchev 039 * @author Sebastien Deleuze 040 * @since 5.0 041 */ 042public class ResponseStatusExceptionHandler implements WebExceptionHandler { 043 044 private static final Log logger = LogFactory.getLog(ResponseStatusExceptionHandler.class); 045 046 047 @Nullable 048 private Log warnLogger; 049 050 051 /** 052 * Set the log category for warn logging. 053 * <p>Default is no warn logging. Specify this setting to activate warn 054 * logging into a specific category. 055 * @since 5.1 056 * @see org.apache.commons.logging.LogFactory#getLog(String) 057 * @see java.util.logging.Logger#getLogger(String) 058 */ 059 public void setWarnLogCategory(String loggerName) { 060 this.warnLogger = LogFactory.getLog(loggerName); 061 } 062 063 064 @Override 065 public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) { 066 if (!updateResponse(exchange.getResponse(), ex)) { 067 return Mono.error(ex); 068 } 069 070 // Mirrors AbstractHandlerExceptionResolver in spring-webmvc... 071 String logPrefix = exchange.getLogPrefix(); 072 if (this.warnLogger != null && this.warnLogger.isWarnEnabled()) { 073 this.warnLogger.warn(logPrefix + formatError(ex, exchange.getRequest()), ex); 074 } 075 else if (logger.isDebugEnabled()) { 076 logger.debug(logPrefix + formatError(ex, exchange.getRequest())); 077 } 078 079 return exchange.getResponse().setComplete(); 080 } 081 082 083 private String formatError(Throwable ex, ServerHttpRequest request) { 084 String reason = ex.getClass().getSimpleName() + ": " + ex.getMessage(); 085 String path = request.getURI().getRawPath(); 086 return "Resolved [" + reason + "] for HTTP " + request.getMethod() + " " + path; 087 } 088 089 private boolean updateResponse(ServerHttpResponse response, Throwable ex) { 090 boolean result = false; 091 HttpStatus status = determineStatus(ex); 092 if (status != null) { 093 if (response.setStatusCode(status)) { 094 if (ex instanceof ResponseStatusException) { 095 ((ResponseStatusException) ex).getResponseHeaders() 096 .forEach((name, values) -> 097 values.forEach(value -> response.getHeaders().add(name, value))); 098 } 099 result = true; 100 } 101 } 102 else { 103 Throwable cause = ex.getCause(); 104 if (cause != null) { 105 result = updateResponse(response, cause); 106 } 107 } 108 return result; 109 } 110 111 /** 112 * Determine the HTTP status implied by the given exception. 113 * @param ex the exception to introspect 114 * @return the associated HTTP status, if any 115 * @since 5.0.5 116 */ 117 @Nullable 118 protected HttpStatus determineStatus(Throwable ex) { 119 if (ex instanceof ResponseStatusException) { 120 return ((ResponseStatusException) ex).getStatus(); 121 } 122 return null; 123 } 124 125}