Skip to content

Commit

Permalink
feat(rest-connector): Add flag to get multiple set cookie headers as …
Browse files Browse the repository at this point in the history
…list
  • Loading branch information
ztefanie committed Jan 17, 2025
1 parent 0c34167 commit 7a423fe
Show file tree
Hide file tree
Showing 11 changed files with 128 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.isHeaderGroupingEnabled()));
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,25 @@ public class HttpCommonResultResponseHandler

private final boolean isStoreResponseSelected;

private final boolean isHeaderGroupingEnabled;

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

@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 = this.formatHeaders(response.getHeaders());

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

private Document handleFileResponse(Map<String, String> headers, byte[] content) {
private Map<String, Object> formatHeaders(Header[] headersArray) {
return Arrays.stream(headersArray)
.collect(
Collectors.toMap(
Header::getName,
header -> {
if (isHeaderGroupingEnabled && header.getName().equalsIgnoreCase("Set-Cookie")) {
return new ArrayList<String>(List.of(header.getValue()));
}
return header.getValue();
},
(existingValue, newValue) -> {
if (isHeaderGroupingEnabled
&& existingValue instanceof List
&& ((List<?>) existingValue).getFirst() instanceof String
&& newValue instanceof List
&& ((List<?>) newValue).getFirst() instanceof String) {
((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 +122,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,15 @@ public class HttpCommonRequest {
optional = true)
private String skipEncoding;

@TemplateProperty(
label = "isHeaderGroupingEnabled",
description = "Group incoming headers with same name into a List",
type = TemplateProperty.PropertyType.Hidden,
feel = Property.FeelMode.disabled,
group = "endpoint",
optional = true)
private String isHeaderGroupingEnabled;

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

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

public void setHeaderGroupingEnabled(final String isHeaderGroupingEnabled) {
this.isHeaderGroupingEnabled = isHeaderGroupingEnabled;
}

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 isHeaderGroupingEnabled,
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.setHeaderGroupingEnabled(isHeaderGroupingEnabled);
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" : "isHeaderGroupingEnabled",
"label" : "isHeaderGroupingEnabled",
"description" : "Group incoming headers with same name into a List",
"optional" : true,
"group" : "endpoint",
"binding" : {
"name" : "isHeaderGroupingEnabled",
"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" : "isHeaderGroupingEnabled",
"label" : "isHeaderGroupingEnabled",
"description" : "Group incoming headers with same name into a List",
"optional" : true,
"group" : "endpoint",
"binding" : {
"name" : "isHeaderGroupingEnabled",
"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",
"isHeaderGroupingEnabled"
},
type = HttpJsonFunction.TYPE)
@ElementTemplate(
Expand Down

0 comments on commit 7a423fe

Please sign in to comment.