Fix HawkbitClient multipart data error handling (#2259)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-01-30 13:01:14 +02:00
committed by GitHub
parent 9418eb42cb
commit df0d0ae461

View File

@@ -23,15 +23,19 @@ import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Base64; import java.util.Base64;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import feign.Client; import feign.Client;
import feign.Contract; import feign.Contract;
import feign.Feign; import feign.Feign;
import feign.FeignException;
import feign.Request;
import feign.RequestInterceptor; import feign.RequestInterceptor;
import feign.RequestTemplate; import feign.RequestTemplate;
import feign.codec.Decoder; import feign.codec.Decoder;
@@ -139,6 +143,16 @@ public class HawkbitClient {
} }
} }
private <T> T service0(final Class<T> 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") @SuppressWarnings("unchecked")
private <T> T proxy(final Class<T> serviceType, final T service, final Tenant tenant, final Controller controller) { private <T> T proxy(final Class<T> serviceType, final T service, final Tenant tenant, final Controller controller) {
final ObjectMapper objectMapper = new ObjectMapper(); final ObjectMapper objectMapper = new ObjectMapper();
@@ -146,7 +160,7 @@ public class HawkbitClient {
try { try {
final Class<?>[] parameterTypes = method.getParameterTypes(); final Class<?>[] parameterTypes = method.getParameterTypes();
if (method.getDeclaringClass() == serviceType && List.of(parameterTypes).contains(MultipartFile.class)) { 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 { } else {
return method.invoke(service, args); return method.invoke(service, args);
} }
@@ -156,9 +170,7 @@ public class HawkbitClient {
}); });
} }
private static final String CRLF = "\r\n"; private Object callMultipartFormDataRequest(
private Object processMultipartFormData(
final Method method, final Object[] args, final Method method, final Object[] args,
final Tenant tenant, final Controller controller, final Tenant tenant, final Controller controller,
final Class<?>[] parameterTypes, final ObjectMapper objectMapper) throws IOException { final Class<?>[] parameterTypes, final ObjectMapper objectMapper) throws IOException {
@@ -207,19 +219,59 @@ public class HawkbitClient {
} }
out.write(("--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8)); 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 return method.getReturnType() == ResponseEntity.class
? new ResponseEntity<>( ? new ResponseEntity<>(
deserialize( deserialize(
conn.getInputStream(), conn.getInputStream(),
(Class<?>) ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0], (Class<?>) ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0],
objectMapper), objectMapper),
HttpStatusCode.valueOf(conn.getResponseCode())) HttpStatusCode.valueOf(responseCode))
: deserialize(conn.getInputStream(), method.getReturnType(), objectMapper); : 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); return type == void.class || type == Void.class ? null : objectMapper.readValue(is, type);
} }
private static final String CRLF = "\r\n";
private void writeMultipartFile( private void writeMultipartFile(
final MultipartFile multipartFile, final OutputStream out, final String boundary, final Annotation[] parametersAnnotations) final MultipartFile multipartFile, final OutputStream out, final String boundary, final Annotation[] parametersAnnotations)
throws IOException { throws IOException {
@@ -238,7 +290,6 @@ public class HawkbitClient {
out.write(CRLF.getBytes(StandardCharsets.UTF_8)); out.write(CRLF.getBytes(StandardCharsets.UTF_8));
} }
} }
private void writeSimpleFormData( private void writeSimpleFormData(
final Object arg, final OutputStream out, final String boundary, final Annotation[] parameterAnnotations) throws IOException { final Object arg, final OutputStream out, final String boundary, final Annotation[] parameterAnnotations) throws IOException {
if (arg != null) { if (arg != null) {
@@ -253,7 +304,7 @@ public class HawkbitClient {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <T extends Annotation> T getAnnotation(final Class<T> annotationClass, final Annotation[] annotations) { private static <T extends Annotation> T getAnnotation(final Class<T> annotationClass, final Annotation[] annotations) {
for (final Annotation annotation : annotations) { for (final Annotation annotation : annotations) {
if (annotation.annotationType().equals(annotationClass)) { if (annotation.annotationType().equals(annotationClass)) {
return (T) annotation; return (T) annotation;
@@ -261,14 +312,4 @@ public class HawkbitClient {
} }
return null; return null;
} }
private <T> T service0(final Class<T> 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());
}
} }