From dd9a98da6c66a3d58c1ad24f83138931ab459dc9 Mon Sep 17 00:00:00 2001 From: Franck Andriano Date: Sun, 6 Oct 2024 23:56:46 +0200 Subject: [PATCH 01/13] Preparation next release 1.0.14-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 253ad27..8d2bc94 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.jservlet.nexus.backend nexus-backend ${packaging} - 1.0.13 + 1.0.14-SNAPSHOT nexus-backend The Java Nexus BackendService, an advanced and secure Rest Backend Gateway From cf7988e73acadae43953a34613d64a636cd63090 Mon Sep 17 00:00:00 2001 From: Franck Andriano Date: Mon, 7 Oct 2024 00:33:06 +0200 Subject: [PATCH 02/13] Fix prevent RegExp Denial of Service - ReDoS! Update doc --- README.md | 4 ++-- .../nexus/shared/web/filter/WAFFilter.java | 17 +++++++++++++++-- .../nexus/shared/web/filter/WAFPredicate.java | 4 ++-- .../nexus/shared/web/filter/WAFUtils.java | 8 -------- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 70b1b56..6c346b1 100644 --- a/README.md +++ b/README.md @@ -144,8 +144,8 @@ and on specific Methods **GET, POST, PUT, PATCH** and Ant Path pattern: | nexus.backend.api-backend-resource.matchers.matchers2.pattern | /api/streaming/** | | nexus.backend.api-backend-resource.matchers.matchers3.method | POST | | nexus.backend.api-backend-resource.matchers.matchers3.pattern | /api/streaming/** | -| nexus.backend.api-backend-resource.matchers.matchers3.method | Others Methods | -| nexus.backend.api-backend-resource.matchers.matchers3.pattern | Others Pattern | +| nexus.backend.api-backend-resource.matchers.matchersX.method | X Others Methods | +| nexus.backend.api-backend-resource.matchers.matchersX.pattern | X Others Pattern | **Http Responses** are considerate as **Resources**, the Http header **"Accept-Ranges: bytes"** is injected and allow you to use the Http header **'Range:bytes=1-100'** in the request and grabbed only range of Bytes desired.
diff --git a/src/main/java/com/jservlet/nexus/shared/web/filter/WAFFilter.java b/src/main/java/com/jservlet/nexus/shared/web/filter/WAFFilter.java index 2705b61..5f34f02 100644 --- a/src/main/java/com/jservlet/nexus/shared/web/filter/WAFFilter.java +++ b/src/main/java/com/jservlet/nexus/shared/web/filter/WAFFilter.java @@ -38,8 +38,10 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.List; import java.util.Map; import java.util.TreeMap; +import java.util.regex.Pattern; import static com.jservlet.nexus.shared.web.filter.WAFFilter.Reactive.*; @@ -144,7 +146,7 @@ public void doFilter(final ServletRequest request, final ServletResponse respons // Just clean the Json body! String body = IOUtils.toString(wrappedRequest.getReader()); if (!StringUtils.isBlank(body)) { - wrappedRequest.setInputStream(WAFUtils.stripWAFPattern(body, wafPredicate.getWafPatterns()).getBytes()); + wrappedRequest.setInputStream(stripWAFPattern(body, wafPredicate.getWafPatterns()).getBytes()); } } @@ -202,12 +204,23 @@ private Map cleanerParameterMap(Map modifiab int len = values.length; String[] encodedValues = new String[len]; for (int i = 0; i < len; i++) - encodedValues[i] = WAFUtils.stripWAFPattern(values[i], wafPredicate.getWafPatterns()); + encodedValues[i] = stripWAFPattern(values[i], wafPredicate.getWafPatterns()); parameters.put(key, encodedValues); } return parameters; } + public static String stripWAFPattern(String value, List patterns) { + if (value == null) return null; + if (value.length() > 10000) { // Prevent RegExp Denial of Service - ReDoS! + throw new RequestRejectedException("Input value is too long!"); + } + // matcher xssPattern replaceAll ? + for (Pattern pattern : patterns) + value = pattern.matcher(value).replaceAll(""); // Cut! + return value; + } + private void rejectBody(String body) { if (!wafPredicate.getWAFParameterValues().test(body)) { throw new RequestRejectedException( diff --git a/src/main/java/com/jservlet/nexus/shared/web/filter/WAFPredicate.java b/src/main/java/com/jservlet/nexus/shared/web/filter/WAFPredicate.java index e0e06ed..6c87d86 100644 --- a/src/main/java/com/jservlet/nexus/shared/web/filter/WAFPredicate.java +++ b/src/main/java/com/jservlet/nexus/shared/web/filter/WAFPredicate.java @@ -42,7 +42,7 @@ public class WAFPredicate { return x.test(param) && s.test(param) && c.test(param) && f.test(param) && l.test(param); }; final private Predicate WAFParameterValues = (param) -> { - if (param.length() > 1000000) return true; + if (param.length() > 10000) return true; return x.test(param) && s.test(param) && c.test(param) && f.test(param) && l.test(param); }; final private Predicate WAFHeaderNames = (header) -> { @@ -50,7 +50,7 @@ public class WAFPredicate { return x.test(header) && s.test(header) && c.test(header); }; final private Predicate WAFHeaderValues = (header) -> { - if (header.length() > 25000) return true; + if (header.length() > 7000) return true; return x.test(header) && s.test(header) && c.test(header); }; final private Predicate WAFHostnames = (names) -> { diff --git a/src/main/java/com/jservlet/nexus/shared/web/filter/WAFUtils.java b/src/main/java/com/jservlet/nexus/shared/web/filter/WAFUtils.java index 75c8c2c..e9e461f 100644 --- a/src/main/java/com/jservlet/nexus/shared/web/filter/WAFUtils.java +++ b/src/main/java/com/jservlet/nexus/shared/web/filter/WAFUtils.java @@ -86,14 +86,6 @@ public static boolean isWAFPattern(String value, List patterns) { return true; } - public static String stripWAFPattern(String value, List patterns) { - if (value == null) return null; - // matcher xssPattern replaceAll ? - for (Pattern pattern : patterns) - value = pattern.matcher(value).replaceAll(""); - return value; - } - public static void main(String[] args) { String test = "image/avif,image/webp,image/apng,image/svg+xml;q=0.8"; System.out.println(isWAFPattern(test, xssPattern)); From 735e0b2db78efabccac024dda1a4b7d47de38437 Mon Sep 17 00:00:00 2001 From: Franck Andriano Date: Mon, 7 Oct 2024 00:35:02 +0200 Subject: [PATCH 03/13] Fix message No ResourceMatchers configured --- .../nexus/controller/GlobalDefaultExceptionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/jservlet/nexus/controller/GlobalDefaultExceptionHandler.java b/src/main/java/com/jservlet/nexus/controller/GlobalDefaultExceptionHandler.java index c408203..4ff8223 100644 --- a/src/main/java/com/jservlet/nexus/controller/GlobalDefaultExceptionHandler.java +++ b/src/main/java/com/jservlet/nexus/controller/GlobalDefaultExceptionHandler.java @@ -54,7 +54,7 @@ public ResponseEntity handleResourceAccessException(HttpServletRequest reques e.getMessage(), request.getRemoteHost(), request.getMethod(), request.getServletPath(), request.getHeader("User-Agent")); // ByeArray Resource request could not be completed due to a conflict with the current state of the target resource! if (Objects.requireNonNull(e.getMessage()).startsWith("Error while extracting response for type [class java.lang.Object] and content type")) { - String msg = "No ResourceMatchers configured. Open the Method and Uri : " + request.getMethod() + " '" + request.getServletPath() + "' and as ByteArray Resource!"; + String msg = "No ResourceMatchers configured. Open the Method and Uri : " + request.getMethod() + " '" + request.getServletPath() + "' as ByteArray Resource!"; return super.getResponseEntity("409", "ERROR", msg, CONFLICT); } return super.getResponseEntity("503", "ERROR", "Remote Service Unavailable" + e.getMessage(), SERVICE_UNAVAILABLE); From 6b7978ee3b162c9413f779d854518d250549cf2e Mon Sep 17 00:00:00 2001 From: Franck Andriano Date: Mon, 7 Oct 2024 00:48:14 +0200 Subject: [PATCH 04/13] Fix size Buffer Overflow evasion by the Length Update doc --- README.md | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 6c346b1..af9da31 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Requests to a RestApi Backend Server.** ### The Nexus Backend application can be configured by the following keys SpringBoot and Settings properties - **SpringBoot keys application.properties** + **SpringBoot keys application.properties:** | **Keys** | **Default value** | **Descriptions** | |-----------------------------------------------|:------------------|:---------------------------------------------------| @@ -84,7 +84,7 @@ Requests to a RestApi Backend Server.** ### The Nexus-Backend Url Server and miscellaneous options can be configured by the following keys Settings - **Settings keys settings.properties** + **Settings keys settings.properties:** | **Keys** | **Default value** | **Example value** | **Descriptions** | |--------------------------------------------------|:-----------------------------|:--------------------------------|:------------------------------------------------| @@ -134,7 +134,7 @@ Requests to a RestApi Backend Server.** The **ResourceMatchers** Config can be configured on specific ByteArray Resources path and on specific Methods **GET, POST, PUT, PATCH** and Ant Path pattern: -**Settings keys settings.properties** +**Settings keys settings.properties:** | **Keys Methods** and **Keys Path pattern** | **Default value** | |---------------------------------------------------------------|:------------------| @@ -148,7 +148,7 @@ and on specific Methods **GET, POST, PUT, PATCH** and Ant Path pattern: | nexus.backend.api-backend-resource.matchers.matchersX.pattern | X Others Pattern | **Http Responses** are considerate as **Resources**, the Http header **"Accept-Ranges: bytes"** is injected and allow you to use -the Http header **'Range:bytes=1-100'** in the request and grabbed only range of Bytes desired.
+the Http header **'Range: bytes=1-100'** in the request and grabbed only range of Bytes desired.
And the Http Responses didn't come back with a HttpHeader **"Transfer-Encoding: chunked"** cause the header **Content-Length**. **Noted:** For configure **all the Responses** in **Resource** put an empty Method and use the path pattern=/api/** @@ -166,7 +166,7 @@ for all the **Response Json Entity Object**, no more HttpHeader **"Transfer-Enco #### MultipartConfig -**SpringBoot keys application.properties** +**SpringBoot keys application.properties:** | **Keys** | **Default value** | **Example value** | **Descriptions** | |----------------------------------------------|:------------------|:------------------|:--------------------| @@ -181,7 +181,7 @@ This BackendResource can convert a **MultipartFile** to a temporary **Resource** ### The BackendService HttpFactory Client Configuration - **Settings keys settings.properties** + **Settings keys settings.properties:** | **Keys** | **Default value** | **Example value** | **Descriptions** | |-----------------------------------------------------|:------------------|:------------------|:-------------------------------| @@ -216,7 +216,7 @@ by the **Apache Coyote http11 processor** (see coyote Error parsing HTTP request All the Http request with **Cookies, Headers, Parameters and RequestBody** will be filtered and the suspicious **IP address** in fault will be logged. - **Settings keys settings.properties** + **Settings keys settings.properties:** | **Keys** | **Default value** | **Descriptions** | |------------------------------------------------------------|:---------------------------------------|:--------------------------------------| @@ -233,26 +233,26 @@ All the Http request with **Cookies, Headers, Parameters and RequestBody** will | nexus.backend.security.allowUrlEncodedParagraphSeparator | false | Allow url encoded Paragraph Separator | | nexus.backend.security.allowUrlEncodedLineSeparator | false | Allow url encoded Line Separator | +**The WAF Utilities Predicates checked for potential evasion:** + +* XSS script injection +* SQL injection +* Google injection +* Command injection +* File injection +* Link injection + **Implements a WAF Predicate for potential evasion by Headers:** - * HeaderNames / HeaderValues - * ParameterNames / ParameterValues + * Header Names / Header Values + * Parameter Names / Parameter Values * Hostnames **And check for Buffer Overflow evasion by the Length:** - * Parameter Names/Values - * Header Names/Values - * Hostnames - -**The WAF Utilities Predicates checked for potential evasion:** - - * XSS script injection - * SQL injection - * Google injection - * Command injection - * File injection - * Link injection + * Parameter Names 255 characters max. / Values 10.000 characters max. + * Header Names 255 characters max. / Values 7.000 characters max. + * Hostnames 255 characters max. **The WAF Reactive mode configuration:** @@ -263,7 +263,7 @@ All the Http request with **Cookies, Headers, Parameters and RequestBody** will ### Activated the Mutual Authentication or mTLS connection on the HttpFactory Client - **Settings keys settings.properties** *nexus.backend.client.ssl.mtls.enable* at **true** for activated the mTLS connection + **Settings keys settings.properties:** *nexus.backend.client.ssl.mtls.enable* at **true** for activated the mTLS connection | **Keys** | **Default value** | **Descriptions** | |---------------------------------------------|:-----------------------|:--------------------------| @@ -277,7 +277,7 @@ All the Http request with **Cookies, Headers, Parameters and RequestBody** will ### Activated Tomcat Catalina Connector TLS/SSL on a wildcard domain Certificate - **Settings keys settings.properties** + **Settings keys settings.properties:** **SpringBoot key** *nexus.backend.tomcat.connector.https.enable* at **true** for activated the TLS/SSL protocol @@ -295,7 +295,7 @@ All the Http request with **Cookies, Headers, Parameters and RequestBody** will ### Activated Tomcat Catalina Extended AccessLog Valve - **Settings keys settings.properties** + **Settings keys settings.properties:** **StringBoot key** *nexus.backend.tomcat.accesslog.valve.enable* at **true** for activated the Accesslogs From f93bf1f55ab499a009d04b91d8f00c42ce2a6ccf Mon Sep 17 00:00:00 2001 From: Franck Andriano Date: Mon, 7 Oct 2024 01:40:42 +0200 Subject: [PATCH 05/13] Fix visibility stripWAFPattern Update comments --- .../nexus/shared/web/controller/api/ApiBackend.java | 6 +++--- .../com/jservlet/nexus/shared/web/filter/WAFFilter.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/jservlet/nexus/shared/web/controller/api/ApiBackend.java b/src/main/java/com/jservlet/nexus/shared/web/controller/api/ApiBackend.java index 0984599..b14b4d8 100644 --- a/src/main/java/com/jservlet/nexus/shared/web/controller/api/ApiBackend.java +++ b/src/main/java/com/jservlet/nexus/shared/web/controller/api/ApiBackend.java @@ -121,8 +121,8 @@ public final void setBackendService(BackendService backendService, ResourceMatch } /** - * Prepare matchers Methods and Ant paths pattern dedicated only for the Resources (see settings.properties)
- */ + * Prepare matchers Methods and Ant paths pattern dedicated only for the Resources + */ @PostConstruct private void postConstruct() { List requestMatchers = new ArrayList<>(); @@ -171,7 +171,7 @@ public void setPattern(String pattern) { /** * Manage a Request Json Entity Object and a Request Map parameters.
- * Or a MultipartRequest encapsulated a List of BackendResource Json Entity Object and a Request Map parameters
+ * Or a MultipartRequest encapsulated a List of BackendResource and a Request Map parameters, and form Json Entity Object
* And return a Response Entity Object or a ByteArray Resource file or any others content in ByteArray... * * @param body String representing the RequestBody Object, just transfer the RequestBody diff --git a/src/main/java/com/jservlet/nexus/shared/web/filter/WAFFilter.java b/src/main/java/com/jservlet/nexus/shared/web/filter/WAFFilter.java index 5f34f02..1ad3057 100644 --- a/src/main/java/com/jservlet/nexus/shared/web/filter/WAFFilter.java +++ b/src/main/java/com/jservlet/nexus/shared/web/filter/WAFFilter.java @@ -210,7 +210,7 @@ private Map cleanerParameterMap(Map modifiab return parameters; } - public static String stripWAFPattern(String value, List patterns) { + private static String stripWAFPattern(String value, List patterns) { if (value == null) return null; if (value.length() > 10000) { // Prevent RegExp Denial of Service - ReDoS! throw new RequestRejectedException("Input value is too long!"); From 4ca183c7383bfb13c67814be7df2473d1621e1b3 Mon Sep 17 00:00:00 2001 From: Franck Andriano Date: Mon, 7 Oct 2024 23:27:36 +0200 Subject: [PATCH 06/13] Add logger info/warn Config ResourceMatchers Fix logs RestClientException Update comments --- .../nexus/controller/GlobalDefaultExceptionHandler.java | 4 ++-- .../nexus/shared/web/controller/api/ApiBackend.java | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/jservlet/nexus/controller/GlobalDefaultExceptionHandler.java b/src/main/java/com/jservlet/nexus/controller/GlobalDefaultExceptionHandler.java index 4ff8223..3e0adce 100644 --- a/src/main/java/com/jservlet/nexus/controller/GlobalDefaultExceptionHandler.java +++ b/src/main/java/com/jservlet/nexus/controller/GlobalDefaultExceptionHandler.java @@ -54,10 +54,10 @@ public ResponseEntity handleResourceAccessException(HttpServletRequest reques e.getMessage(), request.getRemoteHost(), request.getMethod(), request.getServletPath(), request.getHeader("User-Agent")); // ByeArray Resource request could not be completed due to a conflict with the current state of the target resource! if (Objects.requireNonNull(e.getMessage()).startsWith("Error while extracting response for type [class java.lang.Object] and content type")) { - String msg = "No ResourceMatchers configured. Open the Method and Uri : " + request.getMethod() + " '" + request.getServletPath() + "' as ByteArray Resource!"; + String msg = "No ResourceMatchers configured. Open the Method '" + request.getMethod() + "' and the Uri '" + request.getServletPath() + "' as ByteArray Resource!"; return super.getResponseEntity("409", "ERROR", msg, CONFLICT); } - return super.getResponseEntity("503", "ERROR", "Remote Service Unavailable" + e.getMessage(), SERVICE_UNAVAILABLE); + return super.getResponseEntity("503", "ERROR", "Remote Service Unavailable: " + e.getMessage(), SERVICE_UNAVAILABLE); } @ExceptionHandler(value = { HttpMessageNotReadableException.class }) diff --git a/src/main/java/com/jservlet/nexus/shared/web/controller/api/ApiBackend.java b/src/main/java/com/jservlet/nexus/shared/web/controller/api/ApiBackend.java index b14b4d8..b379d73 100644 --- a/src/main/java/com/jservlet/nexus/shared/web/controller/api/ApiBackend.java +++ b/src/main/java/com/jservlet/nexus/shared/web/controller/api/ApiBackend.java @@ -129,9 +129,13 @@ private void postConstruct() { Map map = matchersConfig.getMatchers(); for (Map.Entry entry : map.entrySet()) { requestMatchers.add(new AntPathRequestMatcher(entry.getValue().getPattern(), entry.getValue().getMethod())); + logger.info("Config ResourceMatchers: {} '{}'", entry.getValue().getMethod(), entry.getValue().getPattern()); } // Mandatory, not an empty RequestMatcher! - if (requestMatchers.isEmpty()) requestMatchers.add(new AntPathRequestMatcher("*/**", null)); + if (requestMatchers.isEmpty()) { + requestMatchers.add(new AntPathRequestMatcher("*/**", null)); + logger.warn("Config ResourceMatchers: No ByteArray Resource specified!"); + } orRequestMatcher = new OrRequestMatcher(requestMatchers); } From 3b4dc978b1b1b9c2edbd1075fae66c9de8a9b9a1 Mon Sep 17 00:00:00 2001 From: Franck Andriano Date: Thu, 10 Oct 2024 23:12:35 +0200 Subject: [PATCH 07/13] Fix support Resource and original CONTENT_TYPE Add new constructor BackendServiceImpl, add boolean isHandleBackendEntity Add inner class Generics EntityBackend Add ApiBackend.getBackendHeaders, Transfer some headers from the Backend as CONTENT_TYPE Return now a Generics ResponseEntity, Body or Resource, and new Headers --- .../nexus/config/ApplicationConfig.java | 7 +-- .../nexus/controller/MockController.java | 2 +- .../service/backend/BackendServiceImpl.java | 47 +++++++++++++++- .../shared/web/controller/api/ApiBackend.java | 54 ++++++++++++++----- .../test/config/ApplicationTestConfig.java | 13 +---- 5 files changed, 94 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/jservlet/nexus/config/ApplicationConfig.java b/src/main/java/com/jservlet/nexus/config/ApplicationConfig.java index 98a50a6..5c68c75 100644 --- a/src/main/java/com/jservlet/nexus/config/ApplicationConfig.java +++ b/src/main/java/com/jservlet/nexus/config/ApplicationConfig.java @@ -89,7 +89,7 @@ public class ApplicationConfig { public BackendService backendService(@Value("${nexus.backend.url}") String backendUrl, RestOperations restOperations, ObjectMapper objectMapper) { - final BackendServiceImpl backendService = new BackendServiceImpl(); + final BackendServiceImpl backendService = new BackendServiceImpl(true); // return a Generics Object! backendService.setBackendURL(backendUrl); backendService.setRestOperations(restOperations); backendService.setObjectMapper(objectMapper); @@ -182,9 +182,10 @@ public void serialize(Double value, JsonGenerator jgen, SerializerProvider unuse } @Bean - public RestOperations backendRestOperations(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) throws Exception { + public RestOperations backendRestOperations(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter, + ClientHttpRequestFactory httpRequestFactory) throws Exception { - RestTemplate restTemplate = new RestTemplate(httpRequestFactory()); + RestTemplate restTemplate = new RestTemplate(httpRequestFactory); // Does not encode the URI template, prevent to re-encode again the Uri with percent encoded in %25 DefaultUriBuilderFactory uriFactory = new DefaultUriBuilderFactory(); diff --git a/src/main/java/com/jservlet/nexus/controller/MockController.java b/src/main/java/com/jservlet/nexus/controller/MockController.java index 7da9a50..1dc4d59 100644 --- a/src/main/java/com/jservlet/nexus/controller/MockController.java +++ b/src/main/java/com/jservlet/nexus/controller/MockController.java @@ -369,7 +369,7 @@ private static HttpHeaders extractHeaders(HttpServletRequest request) { private HttpHeaders filterHeaders(HttpHeaders originalHeaders) { HttpHeaders responseHeaders = new HttpHeaders(); responseHeaders.putAll(originalHeaders); - //chuncked and length headers must not be forwarded + // chunked and length headers must not be forwarded responseHeaders.remove(HttpHeaders.TRANSFER_ENCODING); responseHeaders.remove(HttpHeaders.CONTENT_LENGTH); //responseHeaders.remove("Accept-Encoding"); // Gzip !? diff --git a/src/main/java/com/jservlet/nexus/shared/service/backend/BackendServiceImpl.java b/src/main/java/com/jservlet/nexus/shared/service/backend/BackendServiceImpl.java index 41c49dd..18d531b 100644 --- a/src/main/java/com/jservlet/nexus/shared/service/backend/BackendServiceImpl.java +++ b/src/main/java/com/jservlet/nexus/shared/service/backend/BackendServiceImpl.java @@ -62,6 +62,18 @@ public class BackendServiceImpl implements BackendService { private ObjectMapper objectMapper; + /** + * Return by the default a Json Entity Object or Resource, else if true a Generics Object. + */ + private boolean isHandleBackendEntity = false; + + public BackendServiceImpl() { + } + + public BackendServiceImpl(boolean isHandleBackendEntity) { + this.isHandleBackendEntity = isHandleBackendEntity; + } + public void setBackendURL(String backendURL) { this.backendURL = backendURL; } @@ -347,7 +359,8 @@ private T handleResponse(ResponseEntity exchange) { } } if (responseBody == null) return (T) httpStatus; - if (isHandleHttpState(httpStatus)) return (T) new EntityError<>(responseBody, httpStatus); + if (isHandleHttpState(httpStatus)) return (T) new EntityError<>(responseBody, httpHeaders, httpStatus); + if (isHandleBackendEntity) return (T) new EntityBackend<>(responseBody, httpHeaders, httpStatus); return responseBody; } @@ -444,10 +457,12 @@ private ResponseTypeImpl(Class responseClass, ParameterizedTypeReference p public static class EntityError { private final T body; + private final HttpHeaders headers; private final HttpStatus status; - public EntityError(T body, HttpStatus status) { + public EntityError(T body, HttpHeaders headers, HttpStatus status) { this.body = body; + this.headers = headers; this.status = status; } @@ -455,6 +470,34 @@ public T getBody() { return this.body; } + public HttpHeaders getHttpHeaders() { + return this.headers; + } + + public HttpStatus getStatus() { + return this.status; + } + } + + public static class EntityBackend { + private final T body; + private final HttpHeaders headers; + private final HttpStatus status; + + public EntityBackend(T body, HttpHeaders headers, HttpStatus status) { + this.body = body; + this.headers = headers; + this.status = status; + } + + public T getBody() { + return this.body; + } + + public HttpHeaders getHttpHeaders() { + return this.headers; + } + public HttpStatus getStatus() { return this.status; } diff --git a/src/main/java/com/jservlet/nexus/shared/web/controller/api/ApiBackend.java b/src/main/java/com/jservlet/nexus/shared/web/controller/api/ApiBackend.java index b379d73..9f476e0 100644 --- a/src/main/java/com/jservlet/nexus/shared/web/controller/api/ApiBackend.java +++ b/src/main/java/com/jservlet/nexus/shared/web/controller/api/ApiBackend.java @@ -24,6 +24,7 @@ import com.jservlet.nexus.shared.service.backend.BackendService; import com.jservlet.nexus.shared.service.backend.BackendService.ResponseType; import com.jservlet.nexus.shared.service.backend.BackendServiceImpl.EntityError; +import com.jservlet.nexus.shared.service.backend.BackendServiceImpl.EntityBackend; import com.jservlet.nexus.shared.web.controller.ApiBase; import io.swagger.v3.oas.annotations.Hidden; import org.slf4j.Logger; @@ -82,9 +83,9 @@ * etc... *

*

- * The Http Responses are considerate as Resources, the Http header "Accept-Ranges: bytes" is injected and allow you to use - * the Http header 'Range:bytes=1-100' in the request and grabbed only range of Bytes desired.
- * And the Http Responses didn't come back with a HttpHeader "Transfer-Encoding: chunked" cause the header Content-Length. + * The Http Responses can be considerate as Resources, the Http header "Accept-Ranges: bytes" is injected and allow you to use + * the Http header 'Range:bytes=-1000' in the request and by example grabbed the last 1000 bytes (or a range of Bytes).
+ * And the Http Responses will come back without a "Transfer-Encoding: chunked" HttpHeader cause now the header Content-Length. *

* For configure all the Responses in Resource put eh Method empty and use the path pattern=/api/**
* nexus.backend.api-backend-resource.matchers.matchers1.method=
@@ -176,7 +177,7 @@ public void setPattern(String pattern) { /** * Manage a Request Json Entity Object and a Request Map parameters.
* Or a MultipartRequest encapsulated a List of BackendResource and a Request Map parameters, and form Json Entity Object
- * And return a Response Entity Object or a ByteArray Resource file or any others content in ByteArray... + * And return a ResponseEntity Json Entity Object or a ByteArray Resource file or any others content in ByteArray... * * @param body String representing the RequestBody Object, just transfer the RequestBody * @param method HttpMethod GET, POST, PUT, PATCH or DELETE @@ -185,13 +186,13 @@ public void setPattern(String pattern) { * @throws NexusHttpException Exception when a http request to the backend fails * @throws NexusIllegalUrlException Exception when an illegal url will be requested */ - @RequestMapping(value = "/**", produces = MediaType.APPLICATION_JSON_VALUE) + @RequestMapping(value = "/**") public final Object requestEntity(@RequestBody(required = false) String body, HttpMethod method, HttpServletRequest request) throws NexusHttpException, NexusIllegalUrlException { // MultiValueMap store the MultiPartFiles and the Parameters Map MultiValueMap map = null; try { - // The path within the handler mapping and its query + // Any path within handler mapping without "api/" and with its query String url = ((String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).replaceAll("api/", ""); if (request.getQueryString() != null) url = url + "?" + request.getQueryString(); @@ -205,7 +206,7 @@ public final Object requestEntity(@RequestBody(required = false) String body, Ht method, url, printQueryString(request.getQueryString()), printParameterMap(request.getParameterMap()), body, map.entrySet()); } - // Create a ResponseType Object or Resource + // Create a ResponseType Object or Resource by RequestMatcher ResponseType responseType; if (!orRequestMatcher.matches(request)) { responseType = backendService.createResponseType(Object.class); @@ -213,14 +214,25 @@ public final Object requestEntity(@RequestBody(required = false) String body, Ht responseType = backendService.createResponseType(Resource.class); } - // Return a Json Entity Object or any Resource + // Return a Generics EntityError or a Generics EntityBackend Object obj = backendService.doRequest(url, method, responseType, !map.isEmpty() ? map : body, getAllHeaders(request)); - // Manage an EntityError! - if (obj instanceof EntityError) - return new ResponseEntity<>(((EntityError) obj).getBody(), ((EntityError) obj).getStatus()); + // Manage a Generics EntityError embedded a Json Entity Object! + if (obj instanceof EntityError) { + EntityError entityError = (EntityError) obj; + final HttpHeaders newHeaders = getBackendHeaders(entityError.getHttpHeaders()); + return new ResponseEntity<>(entityError, newHeaders, entityError.getStatus()); + } - return obj; + // Manage a Generics EntityBackend embedded a Json Entity Object or a Resource! + EntityBackend entityBackend = (EntityBackend) obj; + final HttpHeaders newHeaders = getBackendHeaders(entityBackend.getHttpHeaders()); + if (entityBackend.getBody() instanceof Resource) { + Resource resource = (Resource) entityBackend.getBody(); + return new ResponseEntity<>(resource, newHeaders, entityBackend.getStatus()); + } else { + return new ResponseEntity<>(entityBackend.getBody(), newHeaders, entityBackend.getStatus()); + } } catch (NexusResourceNotFoundException e) { // Return an error Message NOT_FOUND return super.getResponseEntity("404", "ERROR", e, HttpStatus.NOT_FOUND); @@ -244,6 +256,24 @@ private static HttpHeaders getAllHeaders(HttpServletRequest request) { return headers; } + /** + * Transfer some headers from the Backend RestOperations + */ + private static HttpHeaders getBackendHeaders(HttpHeaders readHeaders) { + HttpHeaders newHeaders = new HttpHeaders(); + if (readHeaders == null || readHeaders.isEmpty()) return newHeaders; + // Original CONTENT_TYPE for a Resource and its character set if it exists + if (readHeaders.getFirst(HttpHeaders.CONTENT_TYPE) != null) { + newHeaders.set(HttpHeaders.CONTENT_TYPE, readHeaders.getFirst(HttpHeaders.CONTENT_TYPE)); + } else {// ByteArray by default! + newHeaders.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE); + } + // Others useful HttpHeaders SERVER, ETAG, Original DATE Backend Server (!?) + // Not CONTENT_LENGTH, CONTENT_RANGE or TRANSFER_ENCODING. Cause already sent in their own Context. + // case SET_COOKIE need a Store !? + return newHeaders; + } + /** * Print the parameterMap in a "Json" object style */ diff --git a/src/test/java/com/jservlet/nexus/test/config/ApplicationTestConfig.java b/src/test/java/com/jservlet/nexus/test/config/ApplicationTestConfig.java index 038ab21..70d78a0 100644 --- a/src/test/java/com/jservlet/nexus/test/config/ApplicationTestConfig.java +++ b/src/test/java/com/jservlet/nexus/test/config/ApplicationTestConfig.java @@ -131,7 +131,7 @@ public boolean canWrite(@NonNull Class clazz, MediaType mediaType) { @Override public @NonNull List getSupportedMediaTypes() { - return Collections.singletonList(MediaType.APPLICATION_OCTET_STREAM); + return List.of(MediaType.APPLICATION_OCTET_STREAM); } @Override @@ -156,18 +156,9 @@ public boolean canWrite(@NonNull Class clazz, MediaType mediaType) { @Override public String getFilename() { - HttpHeaders httpHeaders = inputMessage.getHeaders(); - String disposition = httpHeaders.getFirst(HttpHeaders.CONTENT_DISPOSITION); - if (disposition != null) { - int filenameIdx = disposition.indexOf("filename"); - if (filenameIdx != -1) { - return disposition.substring(filenameIdx + 9); - } - } - return null; + return inputMessage.getHeaders().getContentDisposition().getFilename(); } }; - } @Override From 48744a2ce3d9f68e09760fc837c182f7552f346a Mon Sep 17 00:00:00 2001 From: Franck Andriano Date: Sat, 12 Oct 2024 14:29:51 +0200 Subject: [PATCH 08/13] Fix WebConfig add a ContentNegotiation MediaType contentType, Add Default Header ContentNegotiation Strategy, APPLICATION_PDF is now a safe Extension Add MediaTypes_commons.properties, commons MediaTypes collection Add new resource include mime/*.properties in pom.xml Fix ApiBackend use directly NativeWebRequest as parameter Add HttpMediaTypeNotAcceptableException in GlobalDefaultExceptionHandler Update Doc --- README.md | 48 ++++++--- pom.xml | 1 + .../jservlet/nexus/config/web/WebConfig.java | 99 +++++++++++++++---- .../GlobalDefaultExceptionHandler.java | 15 +++ .../nexus/shared/web/controller/ApiBase.java | 7 ++ .../shared/web/controller/api/ApiBackend.java | 18 ++-- .../mime/MediaTypes_commons.properties | 64 ++++++++++++ 7 files changed, 214 insertions(+), 38 deletions(-) create mode 100644 src/main/resources/mime/MediaTypes_commons.properties diff --git a/README.md b/README.md index af9da31..3ef1eef 100644 --- a/README.md +++ b/README.md @@ -129,28 +129,31 @@ Requests to a RestApi Backend Server.** **ApiBackend ResponseType** can be now a **ByteArray Resource.** -**Download** any content in a **ByteArray** included **JSON, PDF, Gif, PNG, TEXT, HTML!** +**Download** any content in a **ByteArray** included commons extensions files (see **MediaTypes** section) The **ResourceMatchers** Config can be configured on specific ByteArray Resources path and on specific Methods **GET, POST, PUT, PATCH** and Ant Path pattern: **Settings keys settings.properties:** -| **Keys Methods** and **Keys Path pattern** | **Default value** | -|---------------------------------------------------------------|:------------------| -| nexus.backend.api-backend-resource.matchers.matchers1.method | GET | -| nexus.backend.api-backend-resource.matchers.matchers1.pattern | /api/encoding/** | -| nexus.backend.api-backend-resource.matchers.matchers2.method | GET | -| nexus.backend.api-backend-resource.matchers.matchers2.pattern | /api/streaming/** | -| nexus.backend.api-backend-resource.matchers.matchers3.method | POST | -| nexus.backend.api-backend-resource.matchers.matchers3.pattern | /api/streaming/** | -| nexus.backend.api-backend-resource.matchers.matchersX.method | X Others Methods | -| nexus.backend.api-backend-resource.matchers.matchersX.pattern | X Others Pattern | +| **Keys Methods** and **Keys Path pattern** | **Default value** | **Content-Type** | +|---------------------------------------------------------------|:-----------------------|:--------------------------| +| nexus.backend.api-backend-resource.matchers.1.method | GET | | +| nexus.backend.api-backend-resource.matchers.1.pattern | /api/encoding/** | application/octet-stream | +| nexus.backend.api-backend-resource.matchers.2.method | GET | | +| nexus.backend.api-backend-resource.matchers.2.pattern | /api/streaming/** | text/html;charset=utf-8 | +| nexus.backend.api-backend-resource.matchers.3.method | GET | | +| nexus.backend.api-backend-resource.matchers.3.pattern | /api/time/now | text/html;charset=utf-8 | +| nexus.backend.api-backend-resource.matchers.4.method | GET | | +| nexus.backend.api-backend-resource.matchers.4.pattern | /api/response-headers | text/plain;charset=utf-8 | +| nexus.backend.api-backend-resource.matchers.{name}[X].method | Methods | | +| nexus.backend.api-backend-resource.matchers.{name}[X].pattern | Patterns | | **Http Responses** are considerate as **Resources**, the Http header **"Accept-Ranges: bytes"** is injected and allow you to use the Http header **'Range: bytes=1-100'** in the request and grabbed only range of Bytes desired.
And the Http Responses didn't come back with a HttpHeader **"Transfer-Encoding: chunked"** cause the header **Content-Length**. + **Noted:** For configure **all the Responses** in **Resource** put an empty Method and use the path pattern=/api/** | **Keys Methods** and **Keys Path pattern** | **Default value** | @@ -162,6 +165,29 @@ And the Http Responses didn't come back with a HttpHeader **"Transfer-Encoding: enable the **ShallowEtagHeader Filter** in the configuration for force to calculate the header **Content-Length** for all the **Response Json Entity Object**, no more HttpHeader **"Transfer-Encoding: chunked"**. +**MediaTypes safe extensions** + +The Spring ContentNegotiation load the safe extensions files that can be extended. +A commons MediaTypes properties file is loaded [resources/mime/MediaTypes_commons.properties](https://github.com/javaguru/nexus-backend/blob/master/src/main/resources/mime/MediaTypes_commons.properties) +and can be disabled: + +**Settings keys settings.properties:** + +Default Header ContentNegotiation Strategy: + +| **ContentNegotiation Strategy** | **Default value** | **Descriptions Strategy** | +|---------------------------------------------------------------|:------------------|:----------------------------| +| **Header Strategy** | | | +| nexus.backend.content.negotiation.ignoreAcceptHeader | false | Header Strategy Enabled | +| **Parameter Strategy** | | | +| nexus.backend.content.negotiation.favorParameter | false | Parameter Strategy Disabled | +| nexus.backend.content.negotiation.parameterName | mediaType | | +| **Registered Extensions** | | | +| nexus.backend.content.negotiation.useRegisteredExtensionsOnly | true | Registered Only Enabled | +| **Load commons MediaTypes** | | | +| nexus.backend.content.negotiation.commonMediaTypes | true | Enabled | + + ### The Nexus-Backend provides a full support MultipartRequest and Map parameters inside a form-data HttpRequest #### MultipartConfig diff --git a/pom.xml b/pom.xml index 8d2bc94..2bccd65 100644 --- a/pom.xml +++ b/pom.xml @@ -272,6 +272,7 @@ logo-marianne.svg persistence.xml api-ui/api-docs.yaml + mime/*.properties META-INF/services/javax.servlet.ServletContainerInitializer false diff --git a/src/main/java/com/jservlet/nexus/config/web/WebConfig.java b/src/main/java/com/jservlet/nexus/config/web/WebConfig.java index b1f3e70..cdb04d9 100644 --- a/src/main/java/com/jservlet/nexus/config/web/WebConfig.java +++ b/src/main/java/com/jservlet/nexus/config/web/WebConfig.java @@ -31,17 +31,17 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ResourceLoaderAware; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.*; import org.springframework.core.annotation.Order; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.Environment; import org.springframework.core.env.PropertySource; +import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; +import org.springframework.http.MediaType; import org.springframework.lang.NonNull; +import org.springframework.web.accept.HeaderContentNegotiationStrategy; import org.springframework.web.context.ServletContextAware; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.filter.*; @@ -52,8 +52,12 @@ import javax.servlet.*; import javax.servlet.http.HttpServletRequest; +import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; +import java.util.MissingResourceException; +import java.util.Properties; + /* * Web Mvc Configuration */ @@ -68,18 +72,19 @@ }) @EnableWebMvc //@MultipartConfig(location="/tmp", fileSizeThreshold=1024*1024, maxFileSize=1024*1024*15, maxRequestSize=1024*1024*15) -@SuppressWarnings({ "unchecked", "rawtypes" }) public class WebConfig implements WebMvcConfigurer, ResourceLoaderAware, ServletContextAware, ApplicationContextAware { private final static Logger logger = LoggerFactory.getLogger(WebConfig.class); - private static final String ENV_VAR = "environment"; - private Environment env; private ResourceLoader resourceLoader; + private ServletContext servletContext; - private static ApplicationContext context; + private static ApplicationContext appContext; + + private Environment env; + private static final String ENV_VAR = "environment"; @Autowired public void setEnv(Environment env) { @@ -90,7 +95,7 @@ public void setEnv(Environment env) { @Override public synchronized void setApplicationContext(@NonNull ApplicationContext ac) { logger.info("SpringBoot set ApplicationContext -> ac: ['{}']", ac.getId()); - WebConfig.context = ac; + WebConfig.appContext = ac; if (logger.isInfoEnabled()) { Map map = getApplicationProperties(env); @@ -106,8 +111,8 @@ public synchronized void setApplicationContext(@NonNull ApplicationContext ac) { * Get the current ApplicationContext * @return ApplicationContext */ - public synchronized ApplicationContext getApplicationContext() { - return context; + public static synchronized ApplicationContext getApplicationContext() { + return appContext; } /* Stuff, get loaded Application Properties */ @@ -117,7 +122,7 @@ private static Map getApplicationProperties(Environment env) { for (PropertySource propertySource : ((ConfigurableEnvironment) env).getPropertySources()) { // WARN all tracked springboot config file *.properties! if (propertySource instanceof OriginTrackedMapPropertySource) { - for (String key : ((EnumerablePropertySource) propertySource).getPropertyNames()) { + for (String key : ((EnumerablePropertySource) propertySource).getPropertyNames()) { map.put(key, propertySource.getProperty(key)); } } @@ -181,8 +186,8 @@ public void addViewControllers(ViewControllerRegistry registry) { @Bean @Order(1) @ConditionalOnProperty(value="nexus.backend.filter.forwardedHeader.enabled", havingValue = "true") - public FilterRegistrationBean forwardedInfoFilter() { - FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + public FilterRegistrationBean forwardedInfoFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); ForwardedHeaderFilter filter = new ForwardedHeaderFilter(); filter.setRemoveOnly(forwardedHeaderRemoveOnly); registrationBean.setFilter(filter); @@ -198,8 +203,8 @@ public FilterRegistrationBean forwardedInfoFilter() { @Bean @Order(2) @ConditionalOnProperty(value="nexus.backend.filter.gzip.enabled", havingValue = "true") - public FilterRegistrationBean gzipFilter() { - FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + public FilterRegistrationBean gzipFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new CompressingFilter()); registrationBean.setOrder(2); return registrationBean; @@ -213,8 +218,8 @@ public FilterRegistrationBean gzipFilter() { @Bean @Order(3) @ConditionalOnProperty(value="nexus.backend.filter.cors.enabled", havingValue = "true") - public FilterRegistrationBean corsFilterRegistrationBean() { - FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + public FilterRegistrationBean corsFilterRegistrationBean() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); registrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR, DispatcherType.ASYNC); registrationBean.setFilter(new CorsFilter(request -> { final CorsConfiguration configuration = new CorsConfiguration(); @@ -235,8 +240,8 @@ public FilterRegistrationBean corsFilterRegistrationBean() { @Bean @Order(4) @ConditionalOnProperty(value="nexus.backend.filter.shallowEtag.enabled", havingValue = "true") - public FilterRegistrationBean shallowEtagHeaderFilter() { - FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + public FilterRegistrationBean shallowEtagHeaderFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new ShallowEtagHeaderFilter()); registrationBean.setOrder(4); return registrationBean; @@ -302,4 +307,58 @@ public void addResourceHandlers(@NonNull ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("/static/"); } + + @Value("${nexus.backend.content.negotiation.favorParameter:false}") + private boolean favorParameter; + @Value("${nexus.backend.content.negotiation.parameterName:mediaType}") + private String parameterName; + + @Value("${nexus.backend.content.negotiation.ignoreAcceptHeader:false}") + private boolean ignoreAcceptHeader; + + @Value("${nexus.backend.content.negotiation.useRegisteredExtensionsOnly:true}") + private boolean useRegisteredExtensionsOnly; + + @Value("${nexus.backend.content.negotiation.commonMediaTypes:true}") + private boolean commonMediaTypes; + + + /** + * Configure a default ContentNegotiation with a default HeaderContentNegotiationStrategy (ignoreAcceptHeader at false) + * Force the defaultContentType by application/octet-stream + * @see org.springframework.web.accept.ContentNegotiationManagerFactoryBean + * + * @param configurer The current ContentNegotiationConfigurer + */ + @Override + public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { + configurer // JSON Mandatory forced for Resource 404 not found from the Backend + .defaultContentType(MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM) + //.defaultContentTypeStrategy(new HeaderContentNegotiationStrategy()) + .favorParameter(favorParameter) + .parameterName(parameterName) + .ignoreAcceptHeader(ignoreAcceptHeader) + .useRegisteredExtensionsOnly(useRegisteredExtensionsOnly) + .mediaType("pdf", MediaType.APPLICATION_PDF); // Add pdf as safe extensions by default! + + if (commonMediaTypes) { + extractedMediaTypes(configurer, "classpath:mime/MediaTypes_commons.properties"); + } + } + + @SuppressWarnings("SameParameterValue") + private void extractedMediaTypes(ContentNegotiationConfigurer configurer, String classpath) { + Resource resource = resourceLoader.getResource(classpath); + if (resource.exists()) { + try { + Properties properties = new Properties(); + properties.load(resource.getInputStream()); + properties.forEach((key, value) -> + configurer.mediaType(key.toString(), MediaType.parseMediaType(value.toString()))); + } catch (IOException e) { + logger.error("Failed to load '{}' media types {}", classpath, e.getMessage()); + } + } + } + } diff --git a/src/main/java/com/jservlet/nexus/controller/GlobalDefaultExceptionHandler.java b/src/main/java/com/jservlet/nexus/controller/GlobalDefaultExceptionHandler.java index 3e0adce..3961832 100644 --- a/src/main/java/com/jservlet/nexus/controller/GlobalDefaultExceptionHandler.java +++ b/src/main/java/com/jservlet/nexus/controller/GlobalDefaultExceptionHandler.java @@ -21,9 +21,12 @@ import com.jservlet.nexus.shared.web.controller.ApiBase; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.security.web.firewall.RequestRejectedException; +import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.client.RestClientException; @@ -75,6 +78,18 @@ public ResponseEntity handleRejectedException(HttpServletRequest request, Req return super.getResponseEntity("400", "ERROR", "Request rejected!", BAD_REQUEST); } + /* + * For no acceptable representation for a Resource, but will never more happens with the ContentNegotiation + */ + @ExceptionHandler(value = { HttpMediaTypeNotAcceptableException.class }) + public ResponseEntity handleMediaTypeNotAcceptableException(HttpServletRequest request, HttpMediaTypeNotAcceptableException e) { + logger.error("Intercepted HttpMediaTypeNotAcceptableException: {} RemoteHost: {} RequestURL: {} {} UserAgent: {}", + e.getMessage(), request.getRemoteHost(), request.getMethod(), request.getServletPath(), request.getHeader("User-Agent")); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); // Mandatory forced ContentType can be null + return super.getResponseEntity("406", "ERROR", e, headers, NOT_ACCEPTABLE); + } + /* * Any other's error */ diff --git a/src/main/java/com/jservlet/nexus/shared/web/controller/ApiBase.java b/src/main/java/com/jservlet/nexus/shared/web/controller/ApiBase.java index f179679..875e9f0 100644 --- a/src/main/java/com/jservlet/nexus/shared/web/controller/ApiBase.java +++ b/src/main/java/com/jservlet/nexus/shared/web/controller/ApiBase.java @@ -19,6 +19,7 @@ package com.jservlet.nexus.shared.web.controller; import com.fasterxml.jackson.annotation.JsonInclude; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.AccessDeniedException; @@ -57,6 +58,12 @@ protected final ResponseEntity getResponseEntity(String code, String level, E return new ResponseEntity<>(message, httpStatus); } + protected final ResponseEntity getResponseEntity(String code, String level, Exception e, HttpHeaders headers, HttpStatus httpStatus) { + Message message = getMessageObject(code, level); + message.setMessage(e.getMessage()); + return new ResponseEntity<>(message, headers, httpStatus); + } + protected final ResponseEntity getResponseEntity(String code, String level, HttpStatus httpStatus) { return new ResponseEntity<>(getMessageObject(code, level), httpStatus); } diff --git a/src/main/java/com/jservlet/nexus/shared/web/controller/api/ApiBackend.java b/src/main/java/com/jservlet/nexus/shared/web/controller/api/ApiBackend.java index 9f476e0..5f80fd5 100644 --- a/src/main/java/com/jservlet/nexus/shared/web/controller/api/ApiBackend.java +++ b/src/main/java/com/jservlet/nexus/shared/web/controller/api/ApiBackend.java @@ -42,10 +42,10 @@ import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.*; import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartRequest; import org.springframework.web.servlet.HandlerMapping; -import org.springframework.web.util.WebUtils; import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletRequest; @@ -142,7 +142,7 @@ private void postConstruct() { /** * Inner ConfigurationProperties keys prefixed with 'nexus.backend.api-backend-resource' and - * Lopping on incremental keys 'matchers.matchers[X].method' and 'matchers.matchers[X].pattern' + * Lopping on incremental keys 'matchers.{name}[X].method' and 'matchers.{name}[X].pattern' */ @ConfigurationProperties("nexus.backend.api-backend-resource") public static class ResourceMatchersConfig { @@ -177,17 +177,21 @@ public void setPattern(String pattern) { /** * Manage a Request Json Entity Object and a Request Map parameters.
* Or a MultipartRequest encapsulated a List of BackendResource and a Request Map parameters, and form Json Entity Object
- * And return a ResponseEntity Json Entity Object or a ByteArray Resource file or any others content in ByteArray... + * And return a ResponseEntity Json Entity Object or a ByteArray Resource file or any others content in ByteArray...
+ *
+ * For a @RequestMapping allow headers is set to GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS * * @param body String representing the RequestBody Object, just transfer the RequestBody * @param method HttpMethod GET, POST, PUT, PATCH or DELETE * @param request The current HttpServletRequest + * @param nativeWebRequest The current NativeWebRequest for get the MultipartRequest * @return Object Return a ResponseEntity Object or a ByteArray Resource * @throws NexusHttpException Exception when a http request to the backend fails * @throws NexusIllegalUrlException Exception when an illegal url will be requested */ @RequestMapping(value = "/**") - public final Object requestEntity(@RequestBody(required = false) String body, HttpMethod method, HttpServletRequest request) + public final Object requestEntity(@RequestBody(required = false) String body, HttpMethod method, + HttpServletRequest request, NativeWebRequest nativeWebRequest) throws NexusHttpException, NexusIllegalUrlException { // MultiValueMap store the MultiPartFiles and the Parameters Map MultiValueMap map = null; @@ -196,8 +200,8 @@ public final Object requestEntity(@RequestBody(required = false) String body, Ht String url = ((String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).replaceAll("api/", ""); if (request.getQueryString() != null) url = url + "?" + request.getQueryString(); - // Get the MultipartRequest from the miscellaneous Web utilities NativeRequest! - MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class); + // Get the MultipartRequest from the NativeRequest! + MultipartRequest multipartRequest = nativeWebRequest.getNativeRequest(MultipartRequest.class); map = processMapResources(multipartRequest, request.getParameterMap()); // Optimize logs writing, log methods can take time! @@ -214,7 +218,7 @@ public final Object requestEntity(@RequestBody(required = false) String body, Ht responseType = backendService.createResponseType(Resource.class); } - // Return a Generics EntityError or a Generics EntityBackend + // Return a EntityError or a EntityBackend Object obj = backendService.doRequest(url, method, responseType, !map.isEmpty() ? map : body, getAllHeaders(request)); // Manage a Generics EntityError embedded a Json Entity Object! diff --git a/src/main/resources/mime/MediaTypes_commons.properties b/src/main/resources/mime/MediaTypes_commons.properties new file mode 100644 index 0000000..c44daf1 --- /dev/null +++ b/src/main/resources/mime/MediaTypes_commons.properties @@ -0,0 +1,64 @@ +aac=audio/aac +abw=application/x-abiword +apng=image/apng +arc=application/x-freearc +avif=image/avif +avi=video/x-msvideo +azw=application/vnd.amazon.ebook +bmp=image/bmp +bz=application/x-bzip +bz2=application/x-bzip2 +doc=application/msword +docx=application/vnd.openxmlformats-officedocument.wordprocessingml.document +eot=application/vnd.ms-fontobject +epub=application/epub+zip +gz=application/gzip;application/x-gzip +gif=image/gif +ico=image/vnd.microsoft.icon +ics=text/calendar +jar=application/java-archive +jpeg=image/jpeg +jpg=image/jpeg +js=text/javascript +mid=audio/midi +midi=audio/x-midi +mjs=text/javascript +mp3=audio/mpeg +mp4=video/mp4 +mpeg=video/mpeg +odp=application/vnd.oasis.opendocument.presentation +ods=application/vnd.oasis.opendocument.spreadsheet +odt=application/vnd.oasis.opendocument.text +oga=audio/ogg +ogv=video/ogg +ogx=application/ogg +opus=audio/ogg +otf=font/otf +png=image/png +pdf=application/pdf +ppt=application/vnd.ms-powerpoint +pptx=application/vnd.openxmlformats-officedocument.presentationml.presentation +rar=application/vnd.rar +rtf=application/rtf +svg=image/svg+xml +tar=application/x-tar +tif=image/tiff +tiff=image/tiff +ts=video/mp2t +ttf=font/ttf +vsd=application/vnd.visio +wav=audio/wav +weba=audio/webm +webm=video/webm +webp=image/webp +woff=font/woff +woff2=font/woff2 +xhtml=application/xhtml+xml +xls=application/vnd.ms-excel +xlsx=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet +xml=application/xml +xul=application/vnd.mozilla.xul+xml +zip=application/zip;application/x-zip-compressed +3gp=video/3gpp;audio/3gpp +3g2=video/3gpp2;audio/3gpp2 +7z=application/x-7z-compressed From 23c886db12c6698e1dc35d8eeee38472244e8587 Mon Sep 17 00:00:00 2001 From: Franck Andriano Date: Sat, 12 Oct 2024 14:34:09 +0200 Subject: [PATCH 09/13] Activated for Demo endpoints Stream, Encoding, Time now from postman-echo.com --- src/main/resources/settings.properties | 32 +++++++++++++++++++++----- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/main/resources/settings.properties b/src/main/resources/settings.properties index 3812a8c..e8448e9 100644 --- a/src/main/resources/settings.properties +++ b/src/main/resources/settings.properties @@ -9,22 +9,42 @@ nexus.backend.uri.alive=/get ################################################################## -### ApiBackend can switch Http Response in Json Entity Object or ByteArray Resource and download any content in ByteArray. +### The ApiBackend can switch Http Response in Json Entity Object or ByteArray Resource and download any content in ByteArray. # To manage this behavior an AntMatcher Resources on Http Methods and Ant Paths pattern can be configured: # Examples dedicated to postman-echo.com, here: ## # https://postman-echo.com/encoding/utf8 -#nexus.backend.api-backend-resource.matchers.matchers1.method=GET -#nexus.backend.api-backend-resource.matchers.matchers1.pattern=/api/encoding/** +nexus.backend.api-backend-resource.matchers.1.method=GET +nexus.backend.api-backend-resource.matchers.1.pattern=/api/encoding/** ## # https://postman-echo.com/stream/10 -#nexus.backend.api-backend-resource.matchers.matchers2.method=GET -#nexus.backend.api-backend-resource.matchers.matchers2.pattern=/api/stream/** +nexus.backend.api-backend-resource.matchers.2.method=GET +nexus.backend.api-backend-resource.matchers.2.pattern=/api/stream/** ## -# or Download any content in ByteArray Resource: Json, PDF, Gif, Png, Text, Html etc... +# https://postman-echo.com/time/now +nexus.backend.api-backend-resource.matchers.3.method=GET +nexus.backend.api-backend-resource.matchers.3.pattern=/api/time/now +## +# https://postman-echo.com/response-headers +nexus.backend.api-backend-resource.matchers.4.method=GET +nexus.backend.api-backend-resource.matchers.4.pattern=/api/response-headers +## +# OR Download any content in ByteArray Resource (All method and all the endpoints under /api/**) #nexus.backend.api-backend-resource.matchers.matchers1.method= #nexus.backend.api-backend-resource.matchers.matchers1.pattern=/api/** +#### Spring ContentNegotiation Factory for the Resources from the Backend Server + +## Strategy By default: Header Content Negotiation +#nexus.backend.content.negotiation.ignoreAcceptHeader=false +## favor Parameter disable +#nexus.backend.content.negotiation.favorParameter=false +#nexus.backend.content.negotiation.parameterName=mediaType +## Registered extensions only +#nexus.backend.content.negotiation.useRegisteredExtensionsOnly=true +## Loaded commons MediaTypes PDF, DOC, XLS, Audio, Video +#nexus.backend.content.negotiation.commonMediaTypes=true + ################################################################## ### Activated the WAFFilter reactive mode STRICT, PASSIVE or UNSAFE (default STRICT) From 1992fcee0ca037e50054906d74aab70466f1ce6e Mon Sep 17 00:00:00 2001 From: Franck Andriano Date: Sat, 12 Oct 2024 18:52:45 +0200 Subject: [PATCH 10/13] Fix Demo endpoints Stream, Encoding, Time now from postman-echo.com --- README.md | 2 -- src/main/resources/settings.properties | 10 +++------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3ef1eef..832a8be 100644 --- a/README.md +++ b/README.md @@ -144,8 +144,6 @@ and on specific Methods **GET, POST, PUT, PATCH** and Ant Path pattern: | nexus.backend.api-backend-resource.matchers.2.pattern | /api/streaming/** | text/html;charset=utf-8 | | nexus.backend.api-backend-resource.matchers.3.method | GET | | | nexus.backend.api-backend-resource.matchers.3.pattern | /api/time/now | text/html;charset=utf-8 | -| nexus.backend.api-backend-resource.matchers.4.method | GET | | -| nexus.backend.api-backend-resource.matchers.4.pattern | /api/response-headers | text/plain;charset=utf-8 | | nexus.backend.api-backend-resource.matchers.{name}[X].method | Methods | | | nexus.backend.api-backend-resource.matchers.{name}[X].pattern | Patterns | | diff --git a/src/main/resources/settings.properties b/src/main/resources/settings.properties index e8448e9..a6d0cd6 100644 --- a/src/main/resources/settings.properties +++ b/src/main/resources/settings.properties @@ -25,13 +25,9 @@ nexus.backend.api-backend-resource.matchers.2.pattern=/api/stream/** nexus.backend.api-backend-resource.matchers.3.method=GET nexus.backend.api-backend-resource.matchers.3.pattern=/api/time/now ## -# https://postman-echo.com/response-headers -nexus.backend.api-backend-resource.matchers.4.method=GET -nexus.backend.api-backend-resource.matchers.4.pattern=/api/response-headers -## # OR Download any content in ByteArray Resource (All method and all the endpoints under /api/**) -#nexus.backend.api-backend-resource.matchers.matchers1.method= -#nexus.backend.api-backend-resource.matchers.matchers1.pattern=/api/** +#nexus.backend.api-backend-resource.matchers.1.method= +#nexus.backend.api-backend-resource.matchers.1.pattern=/api/** #### Spring ContentNegotiation Factory for the Resources from the Backend Server @@ -42,7 +38,7 @@ nexus.backend.api-backend-resource.matchers.4.pattern=/api/response-headers #nexus.backend.content.negotiation.parameterName=mediaType ## Registered extensions only #nexus.backend.content.negotiation.useRegisteredExtensionsOnly=true -## Loaded commons MediaTypes PDF, DOC, XLS, Audio, Video +## Loaded commons MediaTypes PDF, DOC, XLS, Audio, video #nexus.backend.content.negotiation.commonMediaTypes=true ################################################################## From d1d0de7c8dedc61564b5bb66db2de8fa1aa6b4cd Mon Sep 17 00:00:00 2001 From: Franck Andriano Date: Sat, 12 Oct 2024 19:01:24 +0200 Subject: [PATCH 11/13] Fix Demo endpoints Stream, Encoding, Time now from postman-echo.com --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 832a8be..6a4af0c 100644 --- a/README.md +++ b/README.md @@ -136,16 +136,16 @@ and on specific Methods **GET, POST, PUT, PATCH** and Ant Path pattern: **Settings keys settings.properties:** -| **Keys Methods** and **Keys Path pattern** | **Default value** | **Content-Type** | -|---------------------------------------------------------------|:-----------------------|:--------------------------| -| nexus.backend.api-backend-resource.matchers.1.method | GET | | -| nexus.backend.api-backend-resource.matchers.1.pattern | /api/encoding/** | application/octet-stream | -| nexus.backend.api-backend-resource.matchers.2.method | GET | | -| nexus.backend.api-backend-resource.matchers.2.pattern | /api/streaming/** | text/html;charset=utf-8 | -| nexus.backend.api-backend-resource.matchers.3.method | GET | | -| nexus.backend.api-backend-resource.matchers.3.pattern | /api/time/now | text/html;charset=utf-8 | -| nexus.backend.api-backend-resource.matchers.{name}[X].method | Methods | | -| nexus.backend.api-backend-resource.matchers.{name}[X].pattern | Patterns | | +| **Keys Methods** and **Keys Path pattern** | **Default value** | **Content-Type** | +|---------------------------------------------------------------|:-----------------------|:-------------------------| +| nexus.backend.api-backend-resource.matchers.1.method | GET | | +| nexus.backend.api-backend-resource.matchers.1.pattern | /api/encoding/** | text/html;charset=utf-8 | +| nexus.backend.api-backend-resource.matchers.2.method | GET | | +| nexus.backend.api-backend-resource.matchers.2.pattern | /api/streaming/** | application/octet-stream | +| nexus.backend.api-backend-resource.matchers.3.method | GET | | +| nexus.backend.api-backend-resource.matchers.3.pattern | /api/time/now | text/html;charset=utf-8 | +| nexus.backend.api-backend-resource.matchers.{name}[X].method | Methods | | +| nexus.backend.api-backend-resource.matchers.{name}[X].pattern | Patterns | | **Http Responses** are considerate as **Resources**, the Http header **"Accept-Ranges: bytes"** is injected and allow you to use the Http header **'Range: bytes=1-100'** in the request and grabbed only range of Bytes desired.
From 44da44058da07a6b5ec20e4dce612ed7bb457212 Mon Sep 17 00:00:00 2001 From: Franck Andriano Date: Sun, 13 Oct 2024 23:46:48 +0200 Subject: [PATCH 12/13] ApiBackend, add TRANSFER_HEADERS SET_COOKIE ApplicationConfig, remove disableCookieManagement Optimize MultipartResolver Strict Servlet compliance Optimize logback.xml org.apache.http --- .../nexus/config/ApplicationConfig.java | 2 +- .../jservlet/nexus/config/web/WebConfig.java | 23 +++++-------- .../shared/web/controller/api/ApiBackend.java | 33 +++++++++++++++---- .../shared/web/filter/WAFRequestWrapper.java | 3 +- src/main/resources/logback.xml | 2 -- 5 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/jservlet/nexus/config/ApplicationConfig.java b/src/main/java/com/jservlet/nexus/config/ApplicationConfig.java index 5c68c75..8cd5ef5 100644 --- a/src/main/java/com/jservlet/nexus/config/ApplicationConfig.java +++ b/src/main/java/com/jservlet/nexus/config/ApplicationConfig.java @@ -338,7 +338,7 @@ public String chooseAlias(Map aliases, Socket socket) .setKeepAliveStrategy(myStrategy) .setRedirectStrategy(new LaxRedirectStrategy()) .setRetryHandler(new DefaultHttpRequestRetryHandler(retryCount, requestSentRetryEnabled)) - .disableCookieManagement() + //.disableCookieManagement() .disableAuthCaching() .disableConnectionState() .build()); diff --git a/src/main/java/com/jservlet/nexus/config/web/WebConfig.java b/src/main/java/com/jservlet/nexus/config/web/WebConfig.java index cdb04d9..5dfbd6e 100644 --- a/src/main/java/com/jservlet/nexus/config/web/WebConfig.java +++ b/src/main/java/com/jservlet/nexus/config/web/WebConfig.java @@ -39,9 +39,10 @@ import org.springframework.core.env.PropertySource; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; +import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.lang.NonNull; -import org.springframework.web.accept.HeaderContentNegotiationStrategy; +import org.springframework.util.StringUtils; import org.springframework.web.context.ServletContextAware; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.filter.*; @@ -53,10 +54,7 @@ import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.MissingResourceException; -import java.util.Properties; +import java.util.*; /* * Web Mvc Configuration @@ -277,21 +275,18 @@ public WebServerFactoryCustomizer enableDef } /** - * Use Servlet 4 style MultipartResolver: StandardServletMultipartResolver - * @return the MultipartResolver + * Use Servlet 4 style MultipartResolver: StandardServletMultipartResolver
+ * Strict Servlet compliance only multipart/form-data + * @return the MultipartResolver, */ @Bean public MultipartResolver multipartResolver() { return new StandardServletMultipartResolver() { + private final Set SUPPORTED_METHODS = EnumSet.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH); @Override public boolean isMultipart(@NonNull HttpServletRequest request) { - if (!"POST".equalsIgnoreCase(request.getMethod()) && - !"PUT".equalsIgnoreCase(request.getMethod()) && - !"PATCH".equalsIgnoreCase(request.getMethod())) { - return false; - } - String contentType = request.getContentType(); - return contentType != null && contentType.toLowerCase().startsWith("multipart/"); + if (!SUPPORTED_METHODS.contains(HttpMethod.resolve(request.getMethod()))) return false; + return StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.MULTIPART_FORM_DATA_VALUE); } }; } diff --git a/src/main/java/com/jservlet/nexus/shared/web/controller/api/ApiBackend.java b/src/main/java/com/jservlet/nexus/shared/web/controller/api/ApiBackend.java index 5f80fd5..4895ac4 100644 --- a/src/main/java/com/jservlet/nexus/shared/web/controller/api/ApiBackend.java +++ b/src/main/java/com/jservlet/nexus/shared/web/controller/api/ApiBackend.java @@ -49,6 +49,7 @@ import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -191,7 +192,7 @@ public void setPattern(String pattern) { */ @RequestMapping(value = "/**") public final Object requestEntity(@RequestBody(required = false) String body, HttpMethod method, - HttpServletRequest request, NativeWebRequest nativeWebRequest) + HttpServletRequest request, HttpServletResponse response, NativeWebRequest nativeWebRequest) throws NexusHttpException, NexusIllegalUrlException { // MultiValueMap store the MultiPartFiles and the Parameters Map MultiValueMap map = null; @@ -261,20 +262,40 @@ private static HttpHeaders getAllHeaders(HttpServletRequest request) { } /** - * Transfer some headers from the Backend RestOperations + * Default List of Headers transfer + */ + private final static List TRANSFER_HEADERS = + List.of(HttpHeaders.SERVER, + HttpHeaders.SET_COOKIE, + HttpHeaders.ETAG, + HttpHeaders.DATE, // transfer as Date-Backend + HttpHeaders.USER_AGENT, + "test" // Test postman-echo + ); + + /** + * Transfer some headers from the Backend RestOperations. + * Not CONTENT_LENGTH, CONTENT_RANGE or TRANSFER_ENCODING. Cause already sent in their own Context. + * Case SET_COOKIE need a Store! */ private static HttpHeaders getBackendHeaders(HttpHeaders readHeaders) { HttpHeaders newHeaders = new HttpHeaders(); if (readHeaders == null || readHeaders.isEmpty()) return newHeaders; - // Original CONTENT_TYPE for a Resource and its character set if it exists + // Original CONTENT_TYPE for a Resource and its charset if it exists if (readHeaders.getFirst(HttpHeaders.CONTENT_TYPE) != null) { newHeaders.set(HttpHeaders.CONTENT_TYPE, readHeaders.getFirst(HttpHeaders.CONTENT_TYPE)); } else {// ByteArray by default! newHeaders.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE); } - // Others useful HttpHeaders SERVER, ETAG, Original DATE Backend Server (!?) - // Not CONTENT_LENGTH, CONTENT_RANGE or TRANSFER_ENCODING. Cause already sent in their own Context. - // case SET_COOKIE need a Store !? + for (String headerName : TRANSFER_HEADERS) { + if (readHeaders.getFirst(headerName) != null) { + if (HttpHeaders.DATE.equals(headerName)) { + newHeaders.add(HttpHeaders.DATE + "-Backend", readHeaders.getFirst(HttpHeaders.DATE)); + } else { + newHeaders.add(headerName, readHeaders.getFirst(headerName)); + } + } + } return newHeaders; } diff --git a/src/main/java/com/jservlet/nexus/shared/web/filter/WAFRequestWrapper.java b/src/main/java/com/jservlet/nexus/shared/web/filter/WAFRequestWrapper.java index fae8708..d090595 100644 --- a/src/main/java/com/jservlet/nexus/shared/web/filter/WAFRequestWrapper.java +++ b/src/main/java/com/jservlet/nexus/shared/web/filter/WAFRequestWrapper.java @@ -64,8 +64,7 @@ public void setParameterMap(Map map) { */ public void setInputStream(byte[] newRawData) { cachedBytes = new ByteArrayOutputStream(newRawData.length); - cachedBytes.write(newRawData, 0, newRawData.length); - //cachedBytes.writeBytes(newRawData); + cachedBytes.writeBytes(newRawData); } @Override diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 1240900..7ba5732 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -42,9 +42,7 @@ - - From 243914aad705b4e20e62198d372f4bc8f83a848a Mon Sep 17 00:00:00 2001 From: Franck Andriano Date: Sun, 13 Oct 2024 23:50:19 +0200 Subject: [PATCH 13/13] Preparation stable release 1.0.13 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2bccd65..672c259 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.jservlet.nexus.backend nexus-backend ${packaging} - 1.0.14-SNAPSHOT + 1.0.14 nexus-backend The Java Nexus BackendService, an advanced and secure Rest Backend Gateway