Skip to content

Commit

Permalink
Merge branch 'main' into add-license-scan-badge
Browse files Browse the repository at this point in the history
  • Loading branch information
mathias-vandaele authored Jan 20, 2025
2 parents 817ef83 + 4231107 commit b568c5b
Show file tree
Hide file tree
Showing 11 changed files with 131 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ public HttpCommonResult execute(
.execute(
apacheRequest,
new HttpCommonResultResponseHandler(
executionEnvironment, request.isStoreResponse()));
executionEnvironment,
request.isStoreResponse(),
request.getGroupSetCookieHeaders()));
if (HttpStatusHelper.isError(result.status())) {
throw ConnectorExceptionMapper.from(result);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
Expand All @@ -51,23 +49,27 @@ public class HttpCommonResultResponseHandler

private final boolean isStoreResponseSelected;

private final boolean groupSetCookieHeaders;

public HttpCommonResultResponseHandler(
@Nullable ExecutionEnvironment executionEnvironment, boolean isStoreResponseSelected) {
@Nullable ExecutionEnvironment executionEnvironment,
boolean isStoreResponseSelected,
boolean groupSetCookieHeaders) {
this.executionEnvironment = executionEnvironment;
this.isStoreResponseSelected = isStoreResponseSelected;
this.fileResponseHandler =
new FileResponseHandler(executionEnvironment, isStoreResponseSelected);
this.groupSetCookieHeaders = groupSetCookieHeaders;
}

@Override
public HttpCommonResult handleResponse(ClassicHttpResponse response) {
int code = response.getCode();
String reason = response.getReasonPhrase();
Map<String, String> headers =
Arrays.stream(response.getHeaders())
.collect(
// Collect the headers into a map ignoring duplicates (Set Cookies for instance)
Collectors.toMap(Header::getName, Header::getValue, (first, second) -> first));
Map<String, Object> headers =
HttpCommonResultResponseHandler.formatHeaders(
response.getHeaders(), this.groupSetCookieHeaders);

if (response.getEntity() != null) {
try (InputStream content = response.getEntity().getContent()) {
if (executionEnvironment instanceof ExecutionEnvironment.SaaSCluster) {
Expand All @@ -88,7 +90,30 @@ public HttpCommonResult handleResponse(ClassicHttpResponse response) {
return new HttpCommonResult(code, headers, null, reason);
}

private Document handleFileResponse(Map<String, String> headers, byte[] content) {
private static Map<String, Object> formatHeaders(
Header[] headersArray, Boolean groupSetCookieHeaders) {
return Arrays.stream(headersArray)
.collect(
Collectors.toMap(
Header::getName,
header -> {
if (groupSetCookieHeaders && header.getName().equalsIgnoreCase("Set-Cookie")) {
return new ArrayList<String>(List.of(header.getValue()));
} else {
return header.getValue();
}
},
(existingValue, newValue) -> {
if (groupSetCookieHeaders
&& existingValue instanceof List
&& newValue instanceof List) {
((List<String>) existingValue).add(((List<String>) newValue).getFirst());
}
return existingValue;
}));
}

private Document handleFileResponse(Map<String, Object> headers, byte[] content) {
var document = fileResponseHandler.handle(headers, content);
LOGGER.debug("Stored response as document. Document reference: {}", document);
return document;
Expand All @@ -99,7 +124,7 @@ private Document handleFileResponse(Map<String, String> headers, byte[] content)
* unwrapped as an ErrorResponse. Otherwise, it will be unwrapped as a HttpCommonResult.
*/
private HttpCommonResult getResultForCloudFunction(
int code, InputStream content, Map<String, String> headers, String reason)
int code, InputStream content, Map<String, Object> headers, String reason)
throws IOException {
if (HttpStatusHelper.isError(code)) {
// unwrap as ErrorResponse
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public Document handleCloudFunctionResult(HttpCommonResult result) {
return null;
}

public Document handle(Map<String, String> headers, byte[] content) {
public Document handle(Map<String, Object> headers, byte[] content) {
if (storeResponseSelected()
&& executionEnvironment instanceof ExecutionEnvironment.StoresDocument env) {
try (var byteArrayInputStream = new ByteArrayInputStream(content)) {
Expand All @@ -72,10 +72,11 @@ public Document handle(Map<String, String> headers, byte[] content) {
return null;
}

private String getContentType(Map<String, String> headers) {
private String getContentType(Map<String, Object> headers) {
return headers.entrySet().stream()
.filter(e -> e.getKey().equalsIgnoreCase(CONTENT_TYPE))
.map(Map.Entry::getValue)
.map(Object::toString)
.findFirst()
.orElse(null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class ConnectorExceptionMapper {
public static ConnectorException from(HttpCommonResult result) {
String status = String.valueOf(result.status());
String reason = Optional.ofNullable(result.reason()).orElse("[no reason]");
Map<String, String> headers = result.headers();
Map<String, Object> headers = result.headers();
Object body = result.body();
Map<String, Object> response = new HashMap<>();
response.put("headers", headers);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,16 @@ public class HttpCommonRequest {
optional = true)
private String skipEncoding;

@TemplateProperty(
label = "Group set-cookie headers to a list",
description =
"Group incoming headers with same name into a List to support <a href=\"https://datatracker.ietf.org/doc/html/rfc6265\">multiple Set-Cookie headers</a>.",
type = TemplateProperty.PropertyType.Hidden,
feel = Property.FeelMode.disabled,
group = "endpoint",
optional = true)
private String groupSetCookieHeaders;

public Object getBody() {
return body;
}
Expand Down Expand Up @@ -156,6 +166,14 @@ public void setSkipEncoding(final String skipEncoding) {
this.skipEncoding = skipEncoding;
}

public boolean getGroupSetCookieHeaders() {
return Objects.equals(groupSetCookieHeaders, "true");
}

public void setGroupSetCookieHeaders(final String groupSetCookieHeaders) {
this.groupSetCookieHeaders = groupSetCookieHeaders;
}

public boolean hasAuthentication() {
return authentication != null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,24 @@
import java.util.Map;

public record HttpCommonResult(
int status, Map<String, String> headers, Object body, String reason, Document document) {
int status, Map<String, Object> headers, Object body, String reason, Document document) {

public HttpCommonResult(int status, Map<String, String> headers, Object body, String reason) {
public HttpCommonResult(int status, Map<String, Object> headers, Object body, String reason) {
this(status, headers, body, reason, null);
}

public HttpCommonResult(
int status, Map<String, String> headers, Object body, Document documentReference) {
int status, Map<String, Object> headers, Object body, Document documentReference) {
this(status, headers, body, null, documentReference);
}

public HttpCommonResult(int status, Map<String, String> headers, Object body) {
public HttpCommonResult(int status, Map<String, Object> headers, Object body) {
this(status, headers, body, null, null);
}

@DataExample(id = "basic", feel = "= body.order.id")
public static HttpCommonResult exampleResult() {
Map<String, String> headers = Map.of("Content-Type", "application/json");
Map<String, Object> headers = Map.of("Content-Type", "application/json");
DocumentReference.CamundaDocumentReference documentReference =
new CamundaDocumentReferenceImpl(
"theStoreId",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,17 @@
import io.camunda.document.store.InMemoryDocumentStore;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpStatus;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.jupiter.params.provider.*;
import org.skyscreamer.jsonassert.JSONAssert;
import org.skyscreamer.jsonassert.JSONCompareMode;
import org.testcontainers.Testcontainers;
Expand Down Expand Up @@ -382,6 +383,34 @@ public void shouldReturn200WithoutBody_whenEmptyGet(WireMockRuntimeInfo wmRuntim
assertThat(result.status()).isEqualTo(200);
}

private static Stream<Arguments> provideTestDataForHeaderTest() {
return Stream.of(
Arguments.of("Set-Cookie", "false", false, "Test-Value-1"),
Arguments.of("Set-Cookie", "true", true, List.of("Test-Value-1", "Test-Value-2")),
Arguments.of("other-than-set-cookie", "false", false, "Test-Value-1"),
Arguments.of("other-than-set-cookie", "true", false, "Test-Value-1"));
}

@ParameterizedTest
@MethodSource("provideTestDataForHeaderTest")
public void shouldReturn200_whenDuplicatedHeadersAsListDisabled(
String headerKey,
String groupSetCookieHeaders,
Boolean expectedDoesReturnList,
Object expectedValue,
WireMockRuntimeInfo wmRuntimeInfo) {
stubFor(get("/path").willReturn(ok().withHeader(headerKey, "Test-Value-1", "Test-Value-2")));
HttpCommonRequest request = new HttpCommonRequest();
request.setMethod(HttpMethod.GET);
request.setUrl(wmRuntimeInfo.getHttpBaseUrl() + "/path");
request.setGroupSetCookieHeaders(groupSetCookieHeaders);
HttpCommonResult result = customApacheHttpClient.execute(request);
assertThat(result).isNotNull();
assertThat(result.status()).isEqualTo(200);
assertThat(result.headers().get(headerKey) instanceof List).isEqualTo(expectedDoesReturnList);
assertThat(result.headers().get(headerKey)).isEqualTo(expectedValue);
}

@Test
public void shouldReturn200WithBody_whenGetWithBody(WireMockRuntimeInfo wmRuntimeInfo)
throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public class HttpCommonResultResponseHandlerTest {
@Test
public void shouldHandleJsonResponse_whenCloudFunctionDisabled() throws Exception {
// given
HttpCommonResultResponseHandler handler = new HttpCommonResultResponseHandler(null, false);
HttpCommonResultResponseHandler handler =
new HttpCommonResultResponseHandler(null, false, false);
ClassicHttpResponse response = new BasicClassicHttpResponse(200);
Header[] headers = new Header[] {new BasicHeader("Content-Type", "application/json")};
response.setHeaders(headers);
Expand All @@ -55,7 +56,8 @@ public void shouldHandleJsonResponse_whenCloudFunctionDisabled() throws Exceptio
@Test
public void shouldHandleTextResponse_whenCloudFunctionDisabled() throws Exception {
// given
HttpCommonResultResponseHandler handler = new HttpCommonResultResponseHandler(null, false);
HttpCommonResultResponseHandler handler =
new HttpCommonResultResponseHandler(null, false, false);
ClassicHttpResponse response = new BasicClassicHttpResponse(200);
Header[] headers = new Header[] {new BasicHeader("Content-Type", "text/plain")};
response.setHeaders(headers);
Expand All @@ -76,7 +78,8 @@ public void shouldHandleTextResponse_whenCloudFunctionDisabled() throws Exceptio
public void shouldHandleJsonResponse_whenCloudFunctionEnabled() throws Exception {
// given
HttpCommonResultResponseHandler handler =
new HttpCommonResultResponseHandler(new ExecutionEnvironment.SaaSCluster(null), false);
new HttpCommonResultResponseHandler(
new ExecutionEnvironment.SaaSCluster(null), false, false);
ClassicHttpResponse response = new BasicClassicHttpResponse(201);
Header[] headers = new Header[] {new BasicHeader("Content-Type", "application/json")};
response.setHeaders(headers);
Expand All @@ -101,7 +104,8 @@ public void shouldHandleJsonResponse_whenCloudFunctionEnabled() throws Exception
public void shouldHandleError_whenCloudFunctionEnabled() throws Exception {
// given
HttpCommonResultResponseHandler handler =
new HttpCommonResultResponseHandler(new ExecutionEnvironment.SaaSCluster(null), false);
new HttpCommonResultResponseHandler(
new ExecutionEnvironment.SaaSCluster(null), false, false);
ClassicHttpResponse response = new BasicClassicHttpResponse(500);
Header[] headers =
new Header[] {
Expand Down Expand Up @@ -130,7 +134,8 @@ public void shouldHandleError_whenCloudFunctionEnabled() throws Exception {
public void shouldHandleJsonAsTextResponse_whenCloudFunctionEnabled() throws Exception {
// given
HttpCommonResultResponseHandler handler =
new HttpCommonResultResponseHandler(new ExecutionEnvironment.SaaSCluster(null), false);
new HttpCommonResultResponseHandler(
new ExecutionEnvironment.SaaSCluster(null), false, false);
ClassicHttpResponse response = new BasicClassicHttpResponse(201);
Header[] headers = new Header[] {new BasicHeader("Content-Type", "application/json")};
response.setHeaders(headers);
Expand Down
11 changes: 11 additions & 0 deletions connectors/http/rest/element-templates/http-json-connector.json
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,17 @@
"type" : "zeebe:input"
},
"type" : "Hidden"
}, {
"id" : "groupSetCookieHeaders",
"label" : "Group set-cookie headers to a list",
"description" : "Group incoming headers with same name into a List to support <a href=\"https://datatracker.ietf.org/doc/html/rfc6265\">multiple Set-Cookie headers</a>.",
"optional" : true,
"group" : "endpoint",
"binding" : {
"name" : "groupSetCookieHeaders",
"type" : "zeebe:input"
},
"type" : "Hidden"
}, {
"id" : "connectionTimeoutInSeconds",
"label" : "Connection timeout in seconds",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,17 @@
"type" : "zeebe:input"
},
"type" : "Hidden"
}, {
"id" : "groupSetCookieHeaders",
"label" : "Group set-cookie headers to a list",
"description" : "Group incoming headers with same name into a List to support <a href=\"https://datatracker.ietf.org/doc/html/rfc6265\">multiple Set-Cookie headers</a>.",
"optional" : true,
"group" : "endpoint",
"binding" : {
"name" : "groupSetCookieHeaders",
"type" : "zeebe:input"
},
"type" : "Hidden"
}, {
"id" : "connectionTimeoutInSeconds",
"label" : "Connection timeout in seconds",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"readTimeoutInSeconds",
"writeTimeoutInSeconds",
"body",
"storeResponse"
"storeResponse",
"groupSetCookieHeaders"
},
type = HttpJsonFunction.TYPE)
@ElementTemplate(
Expand Down

0 comments on commit b568c5b

Please sign in to comment.