diff --git a/vertx-web-client/src/main/java/io/vertx/ext/web/client/impl/HttpContext.java b/vertx-web-client/src/main/java/io/vertx/ext/web/client/impl/HttpContext.java index e551fcf531..4245ec0b42 100644 --- a/vertx-web-client/src/main/java/io/vertx/ext/web/client/impl/HttpContext.java +++ b/vertx-web-client/src/main/java/io/vertx/ext/web/client/impl/HttpContext.java @@ -431,7 +431,7 @@ private void handleCreateRequest() { try { boolean multipart = "multipart/form-data".equals(contentType); HttpPostRequestEncoder.EncoderMode encoderMode = this.request.multipartMixed() ? HttpPostRequestEncoder.EncoderMode.RFC1738 : HttpPostRequestEncoder.EncoderMode.HTML5; - multipartForm = new MultipartFormUpload(context, (MultipartForm) this.body, multipart, encoderMode); + multipartForm = new MultipartFormUpload(context, (MultipartForm) this.body, multipart, this.options.getProtocolVersion(), encoderMode); this.body = multipartForm; } catch (Exception e) { fail(e); diff --git a/vertx-web-client/src/main/java/io/vertx/ext/web/client/impl/MultipartFormUpload.java b/vertx-web-client/src/main/java/io/vertx/ext/web/client/impl/MultipartFormUpload.java index f60ab1eca5..d36d12b114 100644 --- a/vertx-web-client/src/main/java/io/vertx/ext/web/client/impl/MultipartFormUpload.java +++ b/vertx-web-client/src/main/java/io/vertx/ext/web/client/impl/MultipartFormUpload.java @@ -17,13 +17,22 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.UnpooledByteBufAllocator; -import io.netty.handler.codec.http.*; -import io.netty.handler.codec.http.multipart.*; +import io.netty.handler.codec.http.DefaultFullHttpRequest; +import io.netty.handler.codec.http.HttpConstants; +import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.multipart.Attribute; +import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory; +import io.netty.handler.codec.http.multipart.FileUpload; +import io.netty.handler.codec.http.multipart.HttpPostRequestEncoder; +import io.netty.handler.codec.http.multipart.MemoryAttribute; +import io.netty.handler.codec.http.multipart.MemoryFileUpload; import io.vertx.core.Context; import io.vertx.core.Handler; import io.vertx.core.MultiMap; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpVersion; import io.vertx.core.http.impl.headers.HeadersAdaptor; import io.vertx.core.streams.ReadStream; import io.vertx.core.streams.impl.InboundBuffer; @@ -51,17 +60,27 @@ public class MultipartFormUpload implements ReadStream { private final InboundBuffer pending; private boolean ended; private final Context context; + private final HttpVersion version; public MultipartFormUpload(Context context, MultipartForm parts, boolean multipart, HttpPostRequestEncoder.EncoderMode encoderMode) throws Exception { + this(context, parts, multipart, HttpVersion.HTTP_1_1, encoderMode); + } + + public MultipartFormUpload(Context context, + MultipartForm parts, + boolean multipart, + HttpVersion version, + HttpPostRequestEncoder.EncoderMode encoderMode) throws Exception { this.context = context; + this.version = version; this.pending = new InboundBuffer<>(context) .handler(this::handleChunk) .drainHandler(v -> run()).pause(); this.request = new DefaultFullHttpRequest( - HttpVersion.HTTP_1_1, + io.netty.handler.codec.http.HttpVersion.HTTP_1_1, //this version is not used in any way io.netty.handler.codec.http.HttpMethod.POST, "/"); parts.getCharset(); @@ -175,8 +194,22 @@ public void run() { } } + /** + * Remove the transfer encoding header if the version is not HTTP/1.0 or HTTP/1.1 as indicated in the + * RFC 7540 Section 8.1.2.2. + * Currently, it is verbose, but making sure this does not need to be fixed for HTTP/3 again. + * + * @return the headers appropriate to include with the form. + */ public MultiMap headers() { - return new HeadersAdaptor(request.headers()); + HeadersAdaptor adaptor = new HeadersAdaptor(request.headers()); + // Currently it is verbose, but making sure this does not need to be fixed for HTTP/3 again. + if (this.encoder.isChunked() && !(this.version == HttpVersion.HTTP_1_1 || this.version == HttpVersion.HTTP_1_0)) { + final long realSize = this.encoder.length(); + adaptor.remove(io.vertx.core.http.HttpHeaders.TRANSFER_ENCODING); + adaptor.set(io.vertx.core.http.HttpHeaders.CONTENT_LENGTH, String.valueOf(realSize)); + } + return adaptor; } @Override diff --git a/vertx-web-client/src/test/java/io/vertx/ext/web/client/MultipartFormUploadTest.java b/vertx-web-client/src/test/java/io/vertx/ext/web/client/MultipartFormUploadTest.java index 66db1eb7f8..19526ad991 100644 --- a/vertx-web-client/src/test/java/io/vertx/ext/web/client/MultipartFormUploadTest.java +++ b/vertx-web-client/src/test/java/io/vertx/ext/web/client/MultipartFormUploadTest.java @@ -19,6 +19,8 @@ import io.vertx.core.Context; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpHeaders; +import io.vertx.core.http.HttpVersion; import io.vertx.ext.unit.Async; import io.vertx.ext.unit.TestContext; import io.vertx.ext.unit.junit.VertxUnitRunner; @@ -40,6 +42,8 @@ import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; @RunWith(VertxUnitRunner.class) public class MultipartFormUploadTest { @@ -124,4 +128,27 @@ private void testFileUpload(TestContext ctx, boolean paused) throws Exception { } }); } + + @Test + public void testCorrectHeadersForH2(TestContext ctx) throws Exception { + Async async = ctx.async(); + Buffer result = Buffer.buffer(); + Context context = vertx.getOrCreateContext(); + String longString = TestUtils.randomAlphaString(10_000); + MultipartForm form = MultipartForm.create().attribute("foo", longString); + MultipartFormUpload upload = new MultipartFormUpload(context, form, false, HttpVersion.HTTP_2, HttpPostRequestEncoder.EncoderMode.RFC1738); + try { + upload.endHandler(v -> { + assertEquals("foo=" + longString, result.toString()); + async.complete(); + }); + upload.handler(result::appendBuffer); + assertFalse(upload.headers().contains(HttpHeaders.TRANSFER_ENCODING)); + assertTrue(upload.headers().contains(HttpHeaders.CONTENT_LENGTH)); + upload.resume(); + context.runOnContext(v -> upload.run()); + } catch (Exception e) { + ctx.fail(e); + } + } }