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

HTTP client form submission #5458

Merged
merged 1 commit into from
Jan 23, 2025
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
25 changes: 25 additions & 0 deletions vertx-core/src/main/asciidoc/http.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,31 @@ no need to set the `Content-Length` of the request up-front.
{@link examples.HTTPExamples#example41}
----

==== Form submissions

You can send http form submissions bodies with the {@link io.vertx.core.http.HttpClientRequest#send(io.vertx.core.http.ClientForm)}
variant.

[source,$lang]
----
{@link examples.HTTPExamples#sendForm}
----

By default, the form is submitted with the `application/x-www-form-urlencoded` content type header. You can set
the `content-type` header to `multipart/form-data` instead

[source,$lang]
----
{@link examples.HTTPExamples#sendMultipart}
----

If you want to upload files and send attributes, you can create a {@link io.vertx.core.http.ClientMultipartForm} instead.

[source,$lang]
----
{@link examples.HTTPExamples#sendMultipartWithFileUpload}
----

==== Request timeouts

You can set an idle timeout to prevent your application from unresponsive servers using {@link io.vertx.core.http.RequestOptions#setIdleTimeout(long)} or {@link io.vertx.core.http.HttpClientRequest#idleTimeout(long)}. When the request does not return any data within the timeout period an exception will fail the result and the request will be reset.
Expand Down
44 changes: 44 additions & 0 deletions vertx-core/src/main/java/examples/HTTPExamples.java
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,50 @@ public void example41(HttpClientRequest request) {
request.end();
}

public void sendForm(HttpClientRequest request) {
ClientForm form = ClientForm.form();
form.attribute("firstName", "Dale");
form.attribute("lastName", "Cooper");

// Submit the form as a form URL encoded body
request
.send(form)
.onSuccess(res -> {
// OK
});
}

public void sendMultipart(HttpClientRequest request) {
ClientForm form = ClientForm.form();
form.attribute("firstName", "Dale");
form.attribute("lastName", "Cooper");

// Submit the form as a multipart form body
request
.putHeader("content-type", "multipart/form-data")
.send(form)
.onSuccess(res -> {
// OK
});
}

public void sendMultipartWithFileUpload(HttpClientRequest request) {
ClientMultipartForm form = ClientMultipartForm.multipartForm()
.attribute("imageDescription", "a very nice image")
.binaryFileUpload(
"imageFile",
"image.jpg",
"/path/to/image",
"image/jpeg");

// Submit the form as a multipart form body
request
.send(form)
.onSuccess(res -> {
// OK
});
}

public void clientIdleTimeout(HttpClient client, int port, String host, String uri, int timeoutMS) {
Future<Buffer> fut = client
.request(new RequestOptions()
Expand Down
79 changes: 79 additions & 0 deletions vertx-core/src/main/java/io/vertx/core/http/ClientForm.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2011-2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.core.http;

import io.vertx.codegen.annotations.Fluent;
import io.vertx.codegen.annotations.GenIgnore;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.MultiMap;
import io.vertx.core.http.impl.ClientMultipartFormImpl;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
* A form: a container for attributes.
*/
@VertxGen
public interface ClientForm {

/**
* @return a blank form
*/
static ClientForm form() {
ClientMultipartFormImpl form = new ClientMultipartFormImpl(false);
form.charset(StandardCharsets.UTF_8);
return form;
}

/**
* @param initial the initial content of the form
* @return a form populated after the {@code initial} multimap
*/
static ClientForm form(MultiMap initial) {
ClientMultipartFormImpl form = new ClientMultipartFormImpl(false);
for (Map.Entry<String, String> attribute : initial) {
form.attribute(attribute.getKey(), attribute.getValue());
}
form.charset(StandardCharsets.UTF_8);
return form;
}

@Fluent
ClientForm attribute(String name, String value);

/**
* Set the {@code charset} to use when encoding the form. The default charset is {@link java.nio.charset.StandardCharsets#UTF_8}.
*
* @param charset the charset to use
* @return a reference to this, so the API can be used fluently
*/
@Fluent
ClientForm charset(String charset);

/**
* Set the {@code charset} to use when encoding the form. The default charset is {@link java.nio.charset.StandardCharsets#UTF_8}.
*
* @param charset the charset to use
* @return a reference to this, so the API can be used fluently
*/
@GenIgnore(GenIgnore.PERMITTED_TYPE)
@Fluent
ClientForm charset(Charset charset);

/**
* @return the charset to use when encoding the form
*/
@GenIgnore(GenIgnore.PERMITTED_TYPE)
Charset charset();

}
121 changes: 121 additions & 0 deletions vertx-core/src/main/java/io/vertx/core/http/ClientMultipartForm.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright (c) 2011-2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.core.http;

import io.vertx.codegen.annotations.Fluent;
import io.vertx.codegen.annotations.GenIgnore;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.impl.ClientMultipartFormImpl;

import java.nio.charset.Charset;

/**
* A multipart form, providing file upload capabilities.
*
* @author <a href="mailto:[email protected]">Julien Viet</a>
*/
@VertxGen
public interface ClientMultipartForm extends ClientForm {

/**
* @return a blank multipart form
*/
static ClientMultipartForm multipartForm() {
return new ClientMultipartFormImpl(true);
}

/**
* {@inheritDoc}
*/
@Fluent
ClientMultipartForm attribute(String name, String value);

/**
* {@inheritDoc}
*/
@Fluent
ClientMultipartForm charset(String charset);

/**
* {@inheritDoc}
*/
@GenIgnore(GenIgnore.PERMITTED_TYPE)
@Fluent
ClientMultipartForm charset(Charset charset);

/**
* Allow or disallow multipart mixed encoding when files are sharing the same file name.
* <br/>
* The default value is {@code true}.
* <br/>
* Set to {@code false} if you want to achieve the behavior for <a href="http://www.w3.org/TR/html5/forms.html#multipart-form-data">HTML5</a>.
*
* @param allow {@code true} allows use of multipart mixed encoding
* @return a reference to this, so the API can be used fluently
*/
@Fluent
ClientMultipartForm mixed(boolean allow);

/**
* @return whether multipart mixed encoding is allowed
*/
boolean mixed();

/**
* Add a text file upload form data part.
*
* @param name name of the parameter
* @param filename filename of the file
* @param mediaType the MIME type of the file
* @param content the content of the file
* @return a reference to this, so the API can be used fluently
*/
@Fluent
ClientMultipartForm textFileUpload(String name, String filename, String mediaType, Buffer content);

/**
* Add a binary file upload form data part.
*
* @param name name of the parameter
* @param filename filename of the file
* @param mediaType the MIME type of the file
* @param content the content of the file
* @return a reference to this, so the API can be used fluently
*/
@Fluent
ClientMultipartForm binaryFileUpload(String name, String filename, String mediaType, Buffer content);

/**
* Add a text file upload form data part.
*
* @param name name of the parameter
* @param filename filename of the file
* @param mediaType the MIME type of the file
* @param pathname the pathname of the file
* @return a reference to this, so the API can be used fluently
*/
@Fluent
ClientMultipartForm textFileUpload(String name, String filename, String mediaType, String pathname);

/**
* Add a binary file upload form data part.
*
* @param name name of the parameter
* @param filename filename of the file
* @param mediaType the MIME type of the file
* @param pathname the pathname of the file
* @return a reference to this, so the API can be used fluently
*/
@Fluent
ClientMultipartForm binaryFileUpload(String name, String filename, String mediaType, String pathname);

}
17 changes: 13 additions & 4 deletions vertx-core/src/main/java/io/vertx/core/http/HttpClientRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ public interface HttpClientRequest extends WriteStream<Buffer> {
/**
* Send the request with an empty body.
*
* @return a future notified when the last bytes of the request is written
* @return a future notified when the HTTP response is available
*/
default Future<HttpClientResponse> send() {
end();
Expand All @@ -323,7 +323,7 @@ default Future<HttpClientResponse> send() {
/**
* Send the request with a string {@code body}.
*
* @return a future notified when the last bytes of the request is written
* @return a future notified when the HTTP response is available
*/
default Future<HttpClientResponse> send(String body) {
end(body);
Expand All @@ -333,20 +333,29 @@ default Future<HttpClientResponse> send(String body) {
/**
* Send the request with a buffer {@code body}.
*
* @return a future notified when the last bytes of the request is written
* @return a future notified when the HTTP response is available
*/
default Future<HttpClientResponse> send(Buffer body) {
end(body);
return response();
}

/**
* Like {@link #send()} but with a {@code form}. The content will be set to {@code application/x-www-form-urlencoded}
* or {@code multipart/form-data} according to the nature of the form.
*
* @param form the form to send
* @return a future notified when the HTTP response is available
*/
Future<HttpClientResponse> send(ClientForm form);

/**
* Send the request with a stream {@code body}.
*
* <p> If the {@link HttpHeaders#CONTENT_LENGTH} is set then the request assumes this is the
* length of the {stream}, otherwise the request will set a chunked {@link HttpHeaders#CONTENT_ENCODING}.
*
* @return a future notified when the last bytes of the request is written
* @return a future notified when the HTTP response is available
*/
default Future<HttpClientResponse> send(ReadStream<Buffer> body) {
MultiMap headers = headers();
Expand Down
6 changes: 6 additions & 0 deletions vertx-core/src/main/java/io/vertx/core/http/HttpHeaders.java
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,12 @@ public interface HttpHeaders {
@GenIgnore(GenIgnore.PERMITTED_TYPE)
CharSequence APPLICATION_X_WWW_FORM_URLENCODED = HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED;

/**
* multipart/form-data header value
*/
@GenIgnore(GenIgnore.PERMITTED_TYPE)
CharSequence MULTIPART_FORM_DATA = HttpHeaderValues.MULTIPART_FORM_DATA;

/**
* chunked header value
*/
Expand Down
Loading
Loading