From df0d0ae461da1c31420d1f97d0368f938ea61a7e Mon Sep 17 00:00:00 2001 From: Avgustin Marinov Date: Thu, 30 Jan 2025 13:01:14 +0200 Subject: [PATCH] Fix HawkbitClient multipart data error handling (#2259) Signed-off-by: Avgustin Marinov --- .../eclipse/hawkbit/sdk/HawkbitClient.java | 77 ++++++++++++++----- 1 file changed, 59 insertions(+), 18 deletions(-) diff --git a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitClient.java b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitClient.java index d1b641de1..9c3b41dca 100644 --- a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitClient.java +++ b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitClient.java @@ -23,15 +23,19 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.UUID; import java.util.function.BiFunction; +import java.util.stream.Collectors; import java.util.stream.Stream; import com.fasterxml.jackson.databind.ObjectMapper; import feign.Client; import feign.Contract; import feign.Feign; +import feign.FeignException; +import feign.Request; import feign.RequestInterceptor; import feign.RequestTemplate; import feign.codec.Decoder; @@ -139,6 +143,16 @@ public class HawkbitClient { } } + private T service0(final Class serviceType, final Tenant tenant, final Controller controller) { + return Feign.builder().client(client) + .encoder(encoder) + .decoder(decoder) + .errorDecoder(errorDecoder) + .contract(contract) + .requestInterceptor(requestInterceptorFn.apply(tenant, controller)) + .target(serviceType, controller == null ? hawkBitServer.getMgmtUrl() : hawkBitServer.getDdiUrl()); + } + @SuppressWarnings("unchecked") private T proxy(final Class serviceType, final T service, final Tenant tenant, final Controller controller) { final ObjectMapper objectMapper = new ObjectMapper(); @@ -146,7 +160,7 @@ public class HawkbitClient { try { final Class[] parameterTypes = method.getParameterTypes(); if (method.getDeclaringClass() == serviceType && List.of(parameterTypes).contains(MultipartFile.class)) { - return processMultipartFormData(method, args, tenant, controller, parameterTypes, objectMapper); + return callMultipartFormDataRequest(method, args, tenant, controller, parameterTypes, objectMapper); } else { return method.invoke(service, args); } @@ -156,9 +170,7 @@ public class HawkbitClient { }); } - private static final String CRLF = "\r\n"; - - private Object processMultipartFormData( + private Object callMultipartFormDataRequest( final Method method, final Object[] args, final Tenant tenant, final Controller controller, final Class[] parameterTypes, final ObjectMapper objectMapper) throws IOException { @@ -207,19 +219,59 @@ public class HawkbitClient { } out.write(("--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8)); } + + final int responseCode = conn.getResponseCode(); + if (responseCode < 200 || responseCode >= 300) { + throw toFeignException(responseCode, conn, Request.create( + Request.HttpMethod.POST, conn.getURL().toString(), + conn.getHeaderFields().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)), + (Request.Body)null, null)); + } + return method.getReturnType() == ResponseEntity.class ? new ResponseEntity<>( deserialize( conn.getInputStream(), (Class) ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0], objectMapper), - HttpStatusCode.valueOf(conn.getResponseCode())) + HttpStatusCode.valueOf(responseCode)) : deserialize(conn.getInputStream(), method.getReturnType(), objectMapper); } - private Object deserialize(final InputStream is, final Class type, final ObjectMapper objectMapper) throws IOException { + + private static FeignException toFeignException(final int responseCode, final HttpURLConnection conn, final Request request) throws IOException { + if (responseCode >= 500 && responseCode < 600) { + // server error + return switch (responseCode) { + case 500 -> new FeignException.InternalServerError(conn.getResponseMessage(), request, null, null); + case 501 -> new FeignException.NotImplemented(conn.getResponseMessage(), request, null, null); + case 502 -> new FeignException.BadGateway(conn.getResponseMessage(), request, null, null); + case 503 -> new FeignException.ServiceUnavailable(conn.getResponseMessage(), request, null, null); + case 504 -> new FeignException.GatewayTimeout(conn.getResponseMessage(), request, null, null); + default -> new FeignException.FeignServerException(responseCode, conn.getResponseMessage(), request, null, null); + }; + } else { + return switch (responseCode) { + case 400 -> new FeignException.BadRequest(conn.getResponseMessage(), request, null, null); + case 401 -> new FeignException.Unauthorized(conn.getResponseMessage(), request, null, null); + case 403 -> new FeignException.Forbidden(conn.getResponseMessage(), request, null, null); + case 404 -> new FeignException.NotFound(conn.getResponseMessage(), request, null, null); + case 405 -> new FeignException.MethodNotAllowed(conn.getResponseMessage(), request, null, null); + case 406 -> new FeignException.NotAcceptable(conn.getResponseMessage(), request, null, null); + case 409 -> new FeignException.Conflict(conn.getResponseMessage(), request, null, null); + case 410 -> new FeignException.Gone(conn.getResponseMessage(), request, null, null); + case 415 -> new FeignException.UnsupportedMediaType(conn.getResponseMessage(), request, null, null); + case 429 -> new FeignException.TooManyRequests(conn.getResponseMessage(), request, null, null); + case 422 -> new FeignException.UnprocessableEntity(conn.getResponseMessage(), request, null, null); + default -> new FeignException.FeignClientException(responseCode, conn.getResponseMessage(), request, null, null); + }; + } + } + + private static Object deserialize(final InputStream is, final Class type, final ObjectMapper objectMapper) throws IOException { return type == void.class || type == Void.class ? null : objectMapper.readValue(is, type); } + private static final String CRLF = "\r\n"; private void writeMultipartFile( final MultipartFile multipartFile, final OutputStream out, final String boundary, final Annotation[] parametersAnnotations) throws IOException { @@ -238,7 +290,6 @@ public class HawkbitClient { out.write(CRLF.getBytes(StandardCharsets.UTF_8)); } } - private void writeSimpleFormData( final Object arg, final OutputStream out, final String boundary, final Annotation[] parameterAnnotations) throws IOException { if (arg != null) { @@ -253,7 +304,7 @@ public class HawkbitClient { } @SuppressWarnings("unchecked") - private T getAnnotation(final Class annotationClass, final Annotation[] annotations) { + private static T getAnnotation(final Class annotationClass, final Annotation[] annotations) { for (final Annotation annotation : annotations) { if (annotation.annotationType().equals(annotationClass)) { return (T) annotation; @@ -261,14 +312,4 @@ public class HawkbitClient { } return null; } - - private T service0(final Class serviceType, final Tenant tenant, final Controller controller) { - return Feign.builder().client(client) - .encoder(encoder) - .decoder(decoder) - .errorDecoder(errorDecoder) - .contract(contract) - .requestInterceptor(requestInterceptorFn.apply(tenant, controller)) - .target(serviceType, controller == null ? hawkBitServer.getMgmtUrl() : hawkBitServer.getDdiUrl()); - } } \ No newline at end of file