Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix HawkbitClient multipart data error handling #2259

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,19 @@
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;
Expand Down Expand Up @@ -139,14 +143,24 @@ private <T> T service(final Class<T> serviceType, final Tenant tenant, final Con
}
}

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")
private <T> T proxy(final Class<T> serviceType, final T service, final Tenant tenant, final Controller controller) {
final ObjectMapper objectMapper = new ObjectMapper();
return (T) Proxy.newProxyInstance(service.getClass().getClassLoader(), new Class<?>[] { serviceType }, (proxy, method, args) -> {
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);
}
Expand All @@ -156,9 +170,7 @@ private <T> T proxy(final Class<T> serviceType, final T service, final Tenant te
});
}

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 {
Expand Down Expand Up @@ -207,19 +219,59 @@ private Object processMultipartFormData(
}
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 {
Expand All @@ -238,7 +290,6 @@ private void writeMultipartFile(
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) {
Expand All @@ -253,22 +304,12 @@ private void writeSimpleFormData(
}

@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) {
if (annotation.annotationType().equals(annotationClass)) {
return (T) annotation;
}
}
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());
}
}