Skip to content

Commit

Permalink
HTTP client form submission.
Browse files Browse the repository at this point in the history
Motivation:

HTTP client form submission has been available for ages in Vert.x Web Client exclusively. Since there is no good reason for that, this has been implemented in HTTP client as well.

Changes:

HTTP client form submission implementation.
  • Loading branch information
vietj committed Jan 23, 2025
1 parent fa915af commit 6b58c32
Show file tree
Hide file tree
Showing 13 changed files with 1,223 additions and 4 deletions.
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:julien@julienviet.com">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

0 comments on commit 6b58c32

Please sign in to comment.