Add initial support for error handling
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2023-2023 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.function.serverless.web;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Oleg Zhurakousky
|
||||
*
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/error")
|
||||
public class ProxyErrorController {
|
||||
|
||||
private final SimpleDateFormat df = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z");
|
||||
|
||||
private final MappingJackson2JsonView view = new MappingJackson2JsonView();
|
||||
|
||||
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
|
||||
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
|
||||
HttpStatus status = getStatus(request);
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
model.put("status", response.getStatus());
|
||||
model.put("error", request.getAttribute(RequestDispatcher.ERROR_MESSAGE));
|
||||
model.put("path", request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI));
|
||||
model.put("timestamp", df.format(new Date()));
|
||||
response.setStatus(status.value());
|
||||
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
|
||||
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
|
||||
}
|
||||
|
||||
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
|
||||
Map<String, Object> model) {
|
||||
ModelAndView modelAndView = new ModelAndView("Whitelabel Error Page", model);
|
||||
modelAndView.setStatus(status);
|
||||
modelAndView.setView(this.view);
|
||||
return modelAndView;
|
||||
}
|
||||
|
||||
protected HttpStatus getStatus(HttpServletRequest request) {
|
||||
Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
|
||||
if (statusCode == null) {
|
||||
return HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
try {
|
||||
return HttpStatus.valueOf(statusCode);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +162,7 @@ public class ProxyHttpServletResponse implements HttpServletResponse {
|
||||
|
||||
@Override
|
||||
public boolean isCommitted() {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.util.List;
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.Servlet;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
@@ -36,10 +37,15 @@ import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.ConfigurableWebApplicationContext;
|
||||
import org.springframework.web.context.support.GenericWebApplicationContext;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
@@ -55,6 +61,8 @@ import org.springframework.web.servlet.DispatcherServlet;
|
||||
*/
|
||||
public class ProxyMvc {
|
||||
|
||||
private static Log LOG = LogFactory.getLog(ProxyMvc.class);
|
||||
|
||||
static final String MVC_RESULT_ATTRIBUTE = ProxyMvc.class.getName().concat(".MVC_RESULT_ATTRIBUTE");
|
||||
|
||||
private final DispatcherServlet servlet;
|
||||
@@ -71,6 +79,8 @@ public class ProxyMvc {
|
||||
AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(applpicationContext);
|
||||
reader.register(componentClasses);
|
||||
|
||||
reader.register(ProxyErrorController.class);
|
||||
|
||||
try {
|
||||
DispatcherServlet servlet = new DispatcherServlet(applpicationContext);
|
||||
servlet.init(new ProxyServletConfig(servletContext));
|
||||
@@ -199,7 +209,40 @@ public class ProxyMvc {
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
|
||||
this.delegateServlet.service(request, response);
|
||||
try {
|
||||
this.delegateServlet.service(request, response);
|
||||
if (((HttpServletResponse) response).getStatus() != HttpStatus.OK.value()) {
|
||||
((ProxyHttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_STATUS_CODE, ((HttpServletResponse) response).getStatus());
|
||||
this.setErrorMessageAttribute((ProxyHttpServletRequest) request, (ProxyHttpServletResponse) response, null);
|
||||
((ProxyHttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_REQUEST_URI, ((ProxyHttpServletRequest) request).getRequestURI());
|
||||
|
||||
((ProxyHttpServletRequest) request).setRequestURI("/error");
|
||||
this.delegateServlet.service(request, response);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
((ProxyHttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_STATUS_CODE, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
this.setErrorMessageAttribute((ProxyHttpServletRequest) request, (ProxyHttpServletResponse) response, e);
|
||||
((ProxyHttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE, e);
|
||||
((ProxyHttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_REQUEST_URI, ((ProxyHttpServletRequest) request).getRequestURI());
|
||||
|
||||
LOG.error("Failed processing the request to: " + ((ProxyHttpServletRequest) request).getRequestURI(), e);
|
||||
((ProxyHttpServletRequest) request).setRequestURI("/error");
|
||||
this.delegateServlet.service(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
private void setErrorMessageAttribute(ProxyHttpServletRequest request, ProxyHttpServletResponse response, Exception exception) {
|
||||
if (exception != null && StringUtils.hasText(exception.getMessage())) {
|
||||
request.setAttribute(RequestDispatcher.ERROR_MESSAGE, exception.getMessage());
|
||||
}
|
||||
else if (StringUtils.hasText(response.getErrorMessage())) {
|
||||
request.setAttribute(RequestDispatcher.ERROR_MESSAGE, response.getErrorMessage());
|
||||
}
|
||||
else {
|
||||
request.setAttribute(RequestDispatcher.ERROR_MESSAGE, HttpStatus.valueOf(response.getStatus()).getReasonPhrase());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cloud.function.test.app.Pet;
|
||||
import org.springframework.cloud.function.test.app.PetStoreSpringAppConfig;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -44,7 +45,7 @@ public class RequestResponseTests {
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
this.mvc = ProxyMvc.INSTANCE(PetStoreSpringAppConfig.class);
|
||||
this.mvc = ProxyMvc.INSTANCE(PetStoreSpringAppConfig.class, ProxyErrorController.class);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@@ -87,6 +88,23 @@ public class RequestResponseTests {
|
||||
assertThat(pet.getName()).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorThrownFromMethod() throws Exception {
|
||||
HttpServletRequest request = new ProxyHttpServletRequest(null, "GET", "/pets/2");
|
||||
ProxyHttpServletResponse response = new ProxyHttpServletResponse();
|
||||
mvc.service(request, response);
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value());
|
||||
assertThat(response.getErrorMessage()).isEqualTo("No such Dog");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorUnexpectedWhitelabel() throws Exception {
|
||||
HttpServletRequest request = new ProxyHttpServletRequest(null, "GET", "/pets/2/3/4");
|
||||
ProxyHttpServletResponse response = new ProxyHttpServletResponse();
|
||||
mvc.service(request, response);
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validatePostWithBody() throws Exception {
|
||||
ProxyHttpServletRequest request = new ProxyHttpServletRequest(null, "POST", "/pets/");
|
||||
|
||||
@@ -20,10 +20,13 @@ import java.security.Principal;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
@@ -63,8 +66,10 @@ public class PetsController {
|
||||
}
|
||||
|
||||
@RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET)
|
||||
public Pet listPets() {
|
||||
System.out.println("=====> Getting pet by id");
|
||||
public Pet listPets(@PathVariable String petId) {
|
||||
if (petId.equals("2")) {
|
||||
throw new DogNotFoundException();
|
||||
}
|
||||
Pet newPet = new Pet();
|
||||
newPet.setId(UUID.randomUUID().toString());
|
||||
newPet.setBreed(PetData.getRandomBreed());
|
||||
@@ -72,4 +77,9 @@ public class PetsController {
|
||||
newPet.setName(PetData.getRandomName());
|
||||
return newPet;
|
||||
}
|
||||
|
||||
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "No such Dog") // 404
|
||||
public class DogNotFoundException extends RuntimeException {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user