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

Improve the multipart encoded mode handling in the rest client #39770

Merged
merged 3 commits into from
Apr 2, 2024
Merged
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
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 @@ -415,8 +415,16 @@

<1> `MultiPartPayloadFormData` custom Object created to match the API contract for calling service which needs to be converted to `ClientMultipartForm`
<2> Adding attribute `jsonPayload` directly to `ClientMultipartForm`
<3> Adding `FileUpload` objects to `ClientMultipartForm` as binaryFileUpload with contentType.

Check warning on line 418 in docs/src/main/asciidoc/rest-client.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Spelling] Use correct American English spelling. Did you really mean 'contentType'? Raw Output: {"message": "[Quarkus.Spelling] Use correct American English spelling. Did you really mean 'contentType'?", "location": {"path": "docs/src/main/asciidoc/rest-client.adoc", "range": {"start": {"line": 418, "column": 72}}}, "severity": "WARNING"}

[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.

Check warning on line 423 in docs/src/main/asciidoc/rest-client.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/rest-client.adoc", "range": {"start": {"line": 423, "column": 74}}}, "severity": "WARNING"}

Check warning on line 423 in docs/src/main/asciidoc/rest-client.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'.", "location": {"path": "docs/src/main/asciidoc/rest-client.adoc", "range": {"start": {"line": 423, "column": 78}}}, "severity": "INFO"}

This configuration can be achieved via the `quarkus.rest-client.multipart-post-encoder-mode` property.

Check warning on line 425 in docs/src/main/asciidoc/rest-client.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'through', 'by', 'from', 'on', or 'by using' rather than 'via' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'through', 'by', 'from', 'on', or 'by using' rather than 'via' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/rest-client.adoc", "range": {"start": {"line": 425, "column": 36}}}, "severity": "WARNING"}
====

=== 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
Loading