Skip to content
Open
Show file tree
Hide file tree
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 @@ -56,6 +56,10 @@ public static MultiValuePart fromText(String name, String value) {
return new MultiValuePart(name, value, null);
}

public static MultiValuePart fromText(String name, String value, String mimeType) {
return new MultiValuePart(name, value, null, mimeType);
}

public static MultiValuePart fromFile(String name, byte[] value, String filename) {
return new MultiValuePart(name, value, filename);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,22 @@ public class HttpClientConfig {

protected boolean useSystemProperties = false;

/**
* The multipart mode to use when building multipart/form-data requests with the Apache HTTP client implementations.
* <p>
* Supported values:
* <ul>
* <li>{@code "STRICT"} - RFC 2046 compliant. Always writes Content-Type headers for all parts,
* including text parts. This is the default and is needed for text parts with a custom mime type to work correctly.</li>
* <li>{@code "BROWSER_COMPATIBLE"} - Mimics browser behavior. Only writes Content-Type headers for parts with a filename.
* This was the default before 8.0. Note that text parts with a custom mime type will not have their Content-Type sent
* in this mode.</li>
* </ul>
* <p>
* This setting has no effect on the Spring WebClient implementation.
*/
protected String multipartMode = "STRICT";

protected FlowableHttpClient httpClient;
protected Runnable closeRunnable;

Expand Down Expand Up @@ -148,6 +164,14 @@ public boolean isUseSystemProperties() {
return useSystemProperties;
}

public String getMultipartMode() {
return multipartMode;
}

public void setMultipartMode(String multipartMode) {
this.multipartMode = multipartMode;
}

public void merge(HttpClientConfig other) {
if (this.connectTimeout != other.getConnectTimeout()) {
setConnectTimeout(other.getConnectTimeout());
Expand All @@ -173,6 +197,10 @@ public void merge(HttpClientConfig other) {
setUseSystemProperties(other.isUseSystemProperties());
}

if (!Objects.equals(this.multipartMode, other.getMultipartMode())) {
setMultipartMode(other.getMultipartMode());
}

if (!Objects.equals(this.httpClient, other.getHttpClient())) {
setHttpClient(other.getHttpClient());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ public class ApacheHttpComponentsFlowableHttpClient implements FlowableHttpClien
protected final Logger logger = LoggerFactory.getLogger(getClass());

protected HttpClientBuilder clientBuilder;
protected HttpMultipartMode multipartMode;
protected int socketTimeout;
protected int connectTimeout;
protected int connectionRequestTimeout;
Expand Down Expand Up @@ -149,6 +150,7 @@ public boolean verify(String s, SSLSession sslSession) {

this.clientBuilder = httpClientBuilder;

this.multipartMode = resolveMultipartMode(config.getMultipartMode());
this.socketTimeout = config.getSocketTimeout();
this.connectTimeout = config.getConnectTimeout();
this.connectionRequestTimeout = config.getConnectionRequestTimeout();
Expand All @@ -157,11 +159,24 @@ public boolean verify(String s, SSLSession sslSession) {
public ApacheHttpComponentsFlowableHttpClient(HttpClientBuilder clientBuilder, int socketTimeout, int connectTimeout,
int connectionRequestTimeout) {
this.clientBuilder = clientBuilder;
this.multipartMode = HttpMultipartMode.STRICT;
this.socketTimeout = socketTimeout;
this.connectTimeout = connectTimeout;
this.connectionRequestTimeout = connectionRequestTimeout;
}

protected static HttpMultipartMode resolveMultipartMode(String mode) {
if (mode == null) {
return HttpMultipartMode.STRICT;
}
return switch (mode.toUpperCase()) {
case "BROWSER_COMPATIBLE" -> HttpMultipartMode.BROWSER_COMPATIBLE;
case "STRICT" -> HttpMultipartMode.STRICT;
default -> throw new FlowableIllegalArgumentException("Unsupported multipart mode: " + mode
+ ". Supported values are: STRICT, BROWSER_COMPATIBLE");
};
}

@Override
public ExecutableHttpRequest prepareRequest(HttpRequest requestInfo) {
try {
Expand Down Expand Up @@ -235,7 +250,7 @@ protected void setRequestEntity(HttpRequest requestInfo, HttpEntityEnclosingRequ
} else if (requestInfo.getMultiValueParts() != null) {
if (MULTIPART_ENTITY_BUILDER_PRESENT) {
MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();
entityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
entityBuilder.setMode(multipartMode);
for (MultiValuePart part : requestInfo.getMultiValueParts()) {
String name = part.getName();
Object value = part.getBody();
Expand All @@ -246,7 +261,11 @@ protected void setRequestEntity(HttpRequest requestInfo, HttpEntityEnclosingRequ
entityBuilder.addBinaryBody(name, (byte[]) value, ContentType.DEFAULT_BINARY, part.getFilename());
}
} else if (value instanceof String) {
entityBuilder.addTextBody(name, (String) value);
if (StringUtils.isNotBlank(part.getMimeType())) {
entityBuilder.addTextBody(name, (String) value, ContentType.create(part.getMimeType()));
} else {
entityBuilder.addTextBody(name, (String) value);
}
} else if (value != null) {
throw new FlowableIllegalArgumentException("Value of type " + value.getClass() + " is not supported as multi part content");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public class ApacheHttpComponents5FlowableHttpClient implements FlowableAsyncHtt

protected HttpAsyncClient client;
protected boolean closeClient;
protected HttpMultipartMode multipartMode;
protected int socketTimeout;
protected int connectTimeout;
protected int connectionRequestTimeout;
Expand Down Expand Up @@ -133,6 +134,7 @@ public ApacheHttpComponents5FlowableHttpClient(HttpClientConfig config, Consumer
this.client = client;
this.closeClient = true;

this.multipartMode = resolveMultipartMode(config.getMultipartMode());
this.socketTimeout = config.getSocketTimeout();
this.connectTimeout = config.getConnectTimeout();
this.connectionRequestTimeout = config.getConnectionRequestTimeout();
Expand All @@ -141,11 +143,25 @@ public ApacheHttpComponents5FlowableHttpClient(HttpClientConfig config, Consumer
public ApacheHttpComponents5FlowableHttpClient(HttpAsyncClient client, int socketTimeout, int connectTimeout,
int connectionRequestTimeout) {
this.client = client;
this.multipartMode = HttpMultipartMode.STRICT;
this.socketTimeout = socketTimeout;
this.connectTimeout = connectTimeout;
this.connectionRequestTimeout = connectionRequestTimeout;
}

protected static HttpMultipartMode resolveMultipartMode(String mode) {
if (mode == null) {
return HttpMultipartMode.STRICT;
}
return switch (mode.toUpperCase()) {
case "BROWSER_COMPATIBLE", "LEGACY" -> HttpMultipartMode.LEGACY;
case "STRICT" -> HttpMultipartMode.STRICT;
case "EXTENDED" -> HttpMultipartMode.EXTENDED;
default -> throw new FlowableIllegalArgumentException("Unsupported multipart mode: " + mode
+ ". Supported values are: STRICT, BROWSER_COMPATIBLE, LEGACY, EXTENDED");
};
}

public void close() {
if (closeClient && client instanceof ModalCloseable) {
((ModalCloseable) client).close(CloseMode.GRACEFUL);
Expand Down Expand Up @@ -220,7 +236,7 @@ protected void setRequestEntity(HttpRequest requestInfo, AsyncRequestBuilder req
}
} else if (requestInfo.getMultiValueParts() != null) {
MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();
entityBuilder.setMode(HttpMultipartMode.LEGACY);
entityBuilder.setMode(multipartMode);
for (MultiValuePart part : requestInfo.getMultiValueParts()) {
String name = part.getName();
Object value = part.getBody();
Expand All @@ -231,7 +247,11 @@ protected void setRequestEntity(HttpRequest requestInfo, AsyncRequestBuilder req
entityBuilder.addBinaryBody(name, (byte[]) value, ContentType.DEFAULT_BINARY, part.getFilename());
}
} else if (value instanceof String) {
entityBuilder.addTextBody(name, (String) value);
if (StringUtils.isNotBlank(part.getMimeType())) {
entityBuilder.addTextBody(name, (String) value, ContentType.create(part.getMimeType()));
} else {
entityBuilder.addTextBody(name, (String) value);
}
} else if (value != null) {
throw new FlowableIllegalArgumentException("Value of type " + value.getClass() + " is not supported as multi part content");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.flowable.http;

import java.time.Duration;
import java.util.stream.Stream;

import org.flowable.http.common.impl.HttpClientConfig;
import org.flowable.http.common.impl.apache.ApacheHttpComponentsFlowableHttpClient;
import org.flowable.http.common.impl.apache.client5.ApacheHttpComponents5FlowableHttpClient;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.support.ParameterDeclarations;

/**
* @author Filip Hrisafov
*/
public class BrowserCompatibleApacheHttpClientArgumentProvider implements ArgumentsProvider {

@Override
public Stream<? extends Arguments> provideArguments(ParameterDeclarations parameters, ExtensionContext context) {
HttpClientConfig config = createClientConfig();
return Stream.of(
Arguments.of(new ApacheHttpComponentsFlowableHttpClient(config)),
Arguments.of(new ApacheHttpComponents5FlowableHttpClient(config))
);
}

protected HttpClientConfig createClientConfig() {
HttpClientConfig config = new HttpClientConfig();
config.setConnectTimeout(Duration.ofSeconds(5));
config.setSocketTimeout(Duration.ofSeconds(5));
config.setConnectionRequestTimeout(Duration.ofSeconds(5));
config.setRequestRetryLimit(5);
config.setDisableCertVerify(true);
config.setMultipartMode("BROWSER_COMPATIBLE");

return config;
}

}
Loading