Skip to content

Commit

Permalink
Merge pull request #39770 from geoand/#39751
Browse files Browse the repository at this point in the history
Improve the multipart encoded mode handling in the rest client
  • Loading branch information
geoand authored Apr 2, 2024
2 parents e00af30 + 137c028 commit eeea8ad
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 2 deletions.
8 changes: 8 additions & 0 deletions docs/src/main/asciidoc/rest-client.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,14 @@ public ClientMultipartForm buildClientMultipartForm(MultiPartPayloadFormData inp
<2> Adding attribute `jsonPayload` directly to `ClientMultipartForm`
<3> Adding `FileUpload` objects to `ClientMultipartForm` as binaryFileUpload with contentType.

[NOTE]
====
When sending multipart data that uses the same name, problems can arise if the client and server do not use the same multipart encoder mode.
By default, the REST Client uses `RFC1738`, but depending on the situation, clients may need to be configured with `HTML5` or `RFC3986` mode.
This configuration can be achieved via the `quarkus.rest-client.multipart-post-encoder-mode` property.
====

=== Sending large payloads

The REST Client is capable of sending arbitrarily large HTTP bodies without buffering the contents in memory, if one of the following types is used:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class RestClientConfig {
EMPTY.connectTimeout = Optional.empty();
EMPTY.readTimeout = Optional.empty();
EMPTY.followRedirects = Optional.empty();
EMPTY.multipartPostEncoderMode = Optional.empty();
EMPTY.proxyAddress = Optional.empty();
EMPTY.proxyUser = Optional.empty();
EMPTY.proxyPassword = Optional.empty();
Expand Down Expand Up @@ -97,6 +98,19 @@ public class RestClientConfig {
@ConfigItem
public Optional<Boolean> followRedirects;

/**
* Mode in which the form data are encoded. Possible values are `HTML5`, `RFC1738` and `RFC3986`.
* The modes are described in the
* <a href="https://netty.io/4.1/api/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.EncoderMode.html">Netty
* documentation</a>
* <p>
* By default, Rest Client Reactive uses RFC1738.
* <p>
* This property is not applicable to the RESTEasy Client.
*/
@ConfigItem
public Optional<String> multipartPostEncoderMode;

/**
* A string value in the form of `<proxyHost>:<proxyPort>` that specifies the HTTP proxy server hostname
* (or IP address) and port for requests of this client to use.
Expand Down Expand Up @@ -287,6 +301,7 @@ public static RestClientConfig load(String configKey) {
instance.connectTimeout = getConfigValue(configKey, "connect-timeout", Long.class);
instance.readTimeout = getConfigValue(configKey, "read-timeout", Long.class);
instance.followRedirects = getConfigValue(configKey, "follow-redirects", Boolean.class);
instance.multipartPostEncoderMode = getConfigValue(configKey, "multipart-post-encoder-mode", String.class);
instance.proxyAddress = getConfigValue(configKey, "proxy-address", String.class);
instance.proxyUser = getConfigValue(configKey, "proxy-user", String.class);
instance.proxyPassword = getConfigValue(configKey, "proxy-password", String.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,14 @@ static QuarkusRestClientBuilder newBuilder() {
*/
QuarkusRestClientBuilder followRedirects(boolean follow);

/**
* Mode in which the form data are encoded. Possible values are `HTML5`, `RFC1738` and `RFC3986`.
* The modes are described in the
* <a href="https://netty.io/4.1/api/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.EncoderMode.html">Netty
* documentation</a>
*/
QuarkusRestClientBuilder multipartPostEncoderMode(String mode);

/**
* Specifies the HTTP proxy hostname/IP address and port to use for requests from client instances.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ public QuarkusRestClientBuilder nonProxyHosts(String nonProxyHosts) {
return this;
}

@Override
public QuarkusRestClientBuilder multipartPostEncoderMode(String mode) {
proxy.multipartPostEncoderMode(mode);
return this;
}

@Override
public QuarkusRestClientBuilder queryParamStyle(QueryParamStyle style) {
proxy.queryParamStyle(style);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
Expand All @@ -34,6 +35,7 @@
import org.jboss.resteasy.reactive.client.impl.ClientBuilderImpl;
import org.jboss.resteasy.reactive.client.impl.ClientImpl;
import org.jboss.resteasy.reactive.client.impl.WebTargetImpl;
import org.jboss.resteasy.reactive.client.impl.multipart.PausableHttpPostRequestEncoder;
import org.jboss.resteasy.reactive.common.jaxrs.ConfigurationImpl;
import org.jboss.resteasy.reactive.common.jaxrs.MultiQueryParamMode;

Expand Down Expand Up @@ -63,6 +65,7 @@ public class RestClientBuilderImpl implements RestClientBuilder {
private boolean followRedirects;
private QueryParamStyle queryParamStyle;

private String multipartPostEncoderMode;
private String proxyHost;
private Integer proxyPort;
private String proxyUser;
Expand Down Expand Up @@ -167,6 +170,11 @@ public RestClientBuilderImpl nonProxyHosts(String nonProxyHosts) {
return this;
}

public RestClientBuilderImpl multipartPostEncoderMode(String mode) {
this.multipartPostEncoderMode = mode;
return this;
}

public RestClientBuilderImpl clientLogger(ClientLogger clientLogger) {
this.clientLogger = clientLogger;
return this;
Expand Down Expand Up @@ -435,6 +443,21 @@ public <T> T build(Class<T> aClass) throws IllegalStateException, RestClientDefi
configureProxy(globalProxy.host, globalProxy.port, restClientsConfig.proxyUser.orElse(null),
restClientsConfig.proxyPassword.orElse(null), restClientsConfig.nonProxyHosts.orElse(null));
}

if (!clientBuilder.getConfiguration().hasProperty(QuarkusRestClientProperties.MULTIPART_ENCODER_MODE)) {
PausableHttpPostRequestEncoder.EncoderMode multipartPostEncoderMode = null;
if (this.multipartPostEncoderMode != null) {
multipartPostEncoderMode = PausableHttpPostRequestEncoder.EncoderMode
.valueOf(this.multipartPostEncoderMode.toUpperCase(Locale.ROOT));
} else if (restClientsConfig.multipartPostEncoderMode.isPresent()) {
multipartPostEncoderMode = PausableHttpPostRequestEncoder.EncoderMode
.valueOf(restClientsConfig.multipartPostEncoderMode.get().toUpperCase(Locale.ROOT));
}
if (multipartPostEncoderMode != null) {
clientBuilder.property(QuarkusRestClientProperties.MULTIPART_ENCODER_MODE, multipartPostEncoderMode);
}
}

ClientImpl client = clientBuilder.build();
WebTargetImpl target = (WebTargetImpl) client.target(uri);
target.setParamConverterProviders(paramConverterProviders);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ void configureBuilder(QuarkusRestClientBuilder builder) {
}

private void configureCustomProperties(QuarkusRestClientBuilder builder) {
Optional<String> encoder = configRoot.multipartPostEncoderMode;
Optional<String> encoder = oneOf(clientConfigByClassName().multipartPostEncoderMode,
clientConfigByConfigKey().multipartPostEncoderMode, configRoot.multipartPostEncoderMode);
if (encoder != null && encoder.isPresent()) {
PausableHttpPostRequestEncoder.EncoderMode mode = PausableHttpPostRequestEncoder.EncoderMode
.valueOf(encoder.get().toUpperCase(Locale.ROOT));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.jboss.resteasy.reactive.client.api;

import org.jboss.resteasy.reactive.client.impl.multipart.PausableHttpPostRequestEncoder;

public class QuarkusRestClientProperties {

/**
Expand Down Expand Up @@ -27,7 +29,7 @@ public class QuarkusRestClientProperties {
public static final String READ_TIMEOUT = "io.quarkus.rest.client.read-timeout";

/**
* See {@link EncoderMode}, RFC1738 by default
* See {@link PausableHttpPostRequestEncoder.EncoderMode}, RFC1738 by default
*/
public static final String MULTIPART_ENCODER_MODE = "io.quarkus.rest.client.multipart-post-encoder-mode";

Expand Down

0 comments on commit eeea8ad

Please sign in to comment.