diff --git a/src/apps/geoserver/wms/src/main/java/org/geoserver/cloud/wms/app/StatusCodeWmsExceptionHandler.java b/src/apps/geoserver/wms/src/main/java/org/geoserver/cloud/wms/app/StatusCodeWmsExceptionHandler.java index 34c5b0ed..c0181554 100644 --- a/src/apps/geoserver/wms/src/main/java/org/geoserver/cloud/wms/app/StatusCodeWmsExceptionHandler.java +++ b/src/apps/geoserver/wms/src/main/java/org/geoserver/cloud/wms/app/StatusCodeWmsExceptionHandler.java @@ -62,13 +62,13 @@ public class StatusCodeWmsExceptionHandler extends WMSServiceExceptionHandler { @Override public void handleServiceException(ServiceException exception, Request request) { - setStausCode(exception, request); + setStatusCode(exception, request); super.handleServiceException(exception, request); } - private void setStausCode(ServiceException exception, Request request) { + protected void setStatusCode(ServiceException exception, Request request) { if (shallSetStatus(request)) { - HttpStatus status = determineStatucCode(exception); + HttpStatus status = determineStatusCode(exception); HttpServletResponse response = request.getHttpResponse(); response.setStatus(status.value()); } @@ -83,22 +83,31 @@ public class StatusCodeWmsExceptionHandler extends WMSServiceExceptionHandler { return propertyResolver.getProperty(ENABLED_PROPERTY, Boolean.class, Boolean.FALSE); } - private HttpStatus determineStatucCode(ServiceException exception) { - // RenderedImageMapOutputFormat does not set a ServiceException code in case of - // rendering timeout, so check the message: - if (exception.getMessage() != null - && exception.getMessage().startsWith("This request used more time than allowed")) { - /* - * The 503 (Service Unavailable) status code indicates that the server is - * currently unable to handle the request due to a temporary overload or - * scheduled maintenance, which will likely be alleviated after some delay. - */ - return HttpStatus.SERVICE_UNAVAILABLE; + private HttpStatus determineStatusCode(ServiceException exception) { + final String code = exception.getCode(); + final String message = exception.getMessage(); + if (code == null && message != null) { + // Some ServiceException do not have code, so check the message instead + if (message.startsWith("This request used more time than allowed")) { + /* + * RenderedImageMapOutputFormat (rendering timeout) + * The 503 (Service Unavailable) status code indicates that the server is + * currently unable to handle the request due to a temporary overload or + * scheduled maintenance, which will likely be alleviated after some delay. + */ + return HttpStatus.SERVICE_UNAVAILABLE; + } else if (message.contains("Content has been requested in one of the following languages:")) { + // Capabilities_1_3_0_Response (the language requested with the query param ACCEPTLANGUAGES is not + // supported) + return HttpStatus.BAD_REQUEST; + } + } else if (code != null) { + return switch (code) { + case MISSING_PARAMETER_VALUE, INVALID_PARAMETER_VALUE, "InvalidCRS" -> HttpStatus.BAD_REQUEST; + case SERVICE_UNAVAILABLE, MAX_MEMORY_EXCEEDED -> HttpStatus.SERVICE_UNAVAILABLE; + default -> HttpStatus.INTERNAL_SERVER_ERROR; + }; } - return switch (exception.getCode()) { - case MISSING_PARAMETER_VALUE, INVALID_PARAMETER_VALUE, "InvalidCRS" -> HttpStatus.BAD_REQUEST; - case SERVICE_UNAVAILABLE, MAX_MEMORY_EXCEEDED -> HttpStatus.SERVICE_UNAVAILABLE; - default -> HttpStatus.INTERNAL_SERVER_ERROR; - }; + return HttpStatus.INTERNAL_SERVER_ERROR; } } diff --git a/src/apps/geoserver/wms/src/test/java/org/geoserver/cloud/wms/app/StatusCodeWmsExceptionHandlerTest.java b/src/apps/geoserver/wms/src/test/java/org/geoserver/cloud/wms/app/StatusCodeWmsExceptionHandlerTest.java new file mode 100644 index 00000000..669cb84c --- /dev/null +++ b/src/apps/geoserver/wms/src/test/java/org/geoserver/cloud/wms/app/StatusCodeWmsExceptionHandlerTest.java @@ -0,0 +1,83 @@ +/* (c) 2025 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ + +package org.geoserver.cloud.wms.app; + +import static org.geoserver.platform.ServiceException.INVALID_PARAMETER_VALUE; +import static org.geoserver.platform.ServiceException.MAX_MEMORY_EXCEEDED; +import static org.geoserver.platform.ServiceException.MISSING_PARAMETER_VALUE; +import static org.geoserver.platform.ServiceException.SERVICE_UNAVAILABLE; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.stream.Stream; +import javax.servlet.http.HttpServletResponse; +import org.geoserver.config.GeoServer; +import org.geoserver.ows.Request; +import org.geoserver.platform.ServiceException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.core.env.PropertyResolver; +import org.springframework.http.HttpStatus; + +public class StatusCodeWmsExceptionHandlerTest { + private StatusCodeWmsExceptionHandler handler; + private PropertyResolver propertyResolver = mock(PropertyResolver.class); + private GeoServer geoServer = mock(GeoServer.class); + + @BeforeEach + void setUp() { + when(propertyResolver.getProperty("geoserver.wms.exceptions.w3cstatus", Boolean.class, Boolean.FALSE)) + .thenReturn(true); + handler = new StatusCodeWmsExceptionHandler(List.of(), geoServer, propertyResolver); + } + + @ParameterizedTest + @MethodSource("exceptionCodeToHttpStatus") + void setStatusCodeWithExceptionCodeTest(String exceptionCode, HttpStatus expectedStatus) { + ServiceException serviceException = new ServiceException("Some message", exceptionCode); + Request request = mock(Request.class); + HttpServletResponse response = mock(HttpServletResponse.class); + when(request.getHttpResponse()).thenReturn(response); + + handler.setStatusCode(serviceException, request); + + verify(response).setStatus(expectedStatus.value()); + } + + private static Stream exceptionCodeToHttpStatus() { + return Stream.of( + Arguments.of(MISSING_PARAMETER_VALUE, HttpStatus.BAD_REQUEST), + Arguments.of(INVALID_PARAMETER_VALUE, HttpStatus.BAD_REQUEST), + Arguments.of("InvalidCRS", HttpStatus.BAD_REQUEST), + Arguments.of(SERVICE_UNAVAILABLE, HttpStatus.SERVICE_UNAVAILABLE), + Arguments.of(MAX_MEMORY_EXCEEDED, HttpStatus.SERVICE_UNAVAILABLE), + Arguments.of("SomeUnknownCode", HttpStatus.INTERNAL_SERVER_ERROR)); + } + + @ParameterizedTest + @MethodSource("exceptionMessageToHttpStatus") + void setStatusCodeWithExceptionMessageTest(String message, HttpStatus expectedStatus) { + ServiceException exception = new ServiceException(message); + Request request = mock(Request.class); + HttpServletResponse response = mock(HttpServletResponse.class); + when(request.getHttpResponse()).thenReturn(response); + + handler.setStatusCode(exception, request); + + verify(response).setStatus(expectedStatus.value()); + } + + private static Stream exceptionMessageToHttpStatus() { + return Stream.of( + Arguments.of("This request used more time than allowed", HttpStatus.SERVICE_UNAVAILABLE), + Arguments.of("Content has been requested in one of the following languages:", HttpStatus.BAD_REQUEST), + Arguments.of("Unknown message", HttpStatus.INTERNAL_SERVER_ERROR)); + } +}