Skip to content

Commit dfe9b0f

Browse files
yawkatasw12
andauthored
Fix ByteBody stream writing for HTTP/2 (#11261)
The writer wrote ByteBufs directly instead of HttpContent, which works for HTTP/1 but not HTTP/2. Also improved error handling. Fixes #11260 --------- Co-authored-by: Alan Shijun Wang <[email protected]>
1 parent 20d00d6 commit dfe9b0f

File tree

3 files changed

+92
-1
lines changed

3 files changed

+92
-1
lines changed

http-client-jdk/src/test/groovy/io/micronaut/http/client/jdk/Http2Spec.groovy

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import io.micronaut.context.annotation.Property
44
import io.micronaut.context.annotation.Requires
55
import io.micronaut.http.HttpRequest
66
import io.micronaut.http.MediaType
7+
import io.micronaut.http.annotation.Consumes
78
import io.micronaut.http.annotation.Controller
89
import io.micronaut.http.annotation.Get
10+
import io.micronaut.http.annotation.Post
911
import io.micronaut.http.annotation.Produces
1012
import io.micronaut.http.client.HttpClient
1113
import io.micronaut.http.client.HttpVersionSelection
@@ -20,6 +22,7 @@ import spock.lang.Specification
2022
@Property(name = "micronaut.server.ssl.build-self-signed", value = "true")
2123
@Property(name = "micronaut.server.ssl.enabled", value = "true")
2224
@Property(name = "micronaut.http.client.ssl.insecure-trust-all-certificates", value = "true")
25+
@Property(name = "micronaut.server.max-request-size", value = "16384")
2326
class Http2Spec extends Specification {
2427

2528
@Inject
@@ -34,6 +37,18 @@ class Http2Spec extends Specification {
3437
response == "hello"
3538
}
3639

40+
def "test http2 post long string"() {
41+
when:
42+
def body = Map.of("q", "a" * 8097)
43+
44+
def response = client.toBlocking().retrieve(HttpRequest.POST("/http2", body)
45+
.contentType(MediaType.APPLICATION_FORM_URLENCODED_TYPE))
46+
47+
then:
48+
response == "hello"
49+
}
50+
51+
3752
@Controller("/http2")
3853
@Requires(property = "spec.name", value = "Http2Spec")
3954
static class SpecController {
@@ -43,5 +58,12 @@ class Http2Spec extends Specification {
4358
String get() {
4459
"hello"
4560
}
61+
62+
@Post
63+
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
64+
@Produces(MediaType.TEXT_PLAIN)
65+
String post() {
66+
"hello"
67+
}
4668
}
4769
}

http-client/src/main/java/io/micronaut/http/client/netty/StreamWriter.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.netty.channel.ChannelFutureListener;
2424
import io.netty.channel.ChannelHandlerContext;
2525
import io.netty.channel.ChannelInboundHandlerAdapter;
26+
import io.netty.handler.codec.http.DefaultHttpContent;
2627
import io.netty.handler.codec.http.LastHttpContent;
2728

2829
import java.util.function.Consumer;
@@ -99,14 +100,16 @@ private void add0(ByteBuf buf) {
99100
}
100101

101102
int readable = buf.readableBytes();
102-
ctx.writeAndFlush(buf).addListener((ChannelFutureListener) future -> {
103+
ctx.writeAndFlush(new DefaultHttpContent(buf)).addListener((ChannelFutureListener) future -> {
103104
assert ctx.executor().inEventLoop();
104105
if (future.isSuccess()) {
105106
if (ctx.channel().isWritable()) {
106107
upstream.onBytesConsumed(readable);
107108
} else {
108109
unwritten += readable;
109110
}
111+
} else {
112+
error(future.cause());
110113
}
111114
});
112115
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package io.micronaut.http.client.netty
2+
3+
import io.micronaut.context.annotation.Property
4+
import io.micronaut.context.annotation.Requires
5+
import io.micronaut.http.HttpRequest
6+
import io.micronaut.http.MediaType
7+
import io.micronaut.http.annotation.*
8+
import io.micronaut.http.client.HttpClient
9+
import io.micronaut.http.client.HttpVersionSelection
10+
import io.micronaut.http.client.annotation.Client
11+
import io.micronaut.test.extensions.spock.annotation.MicronautTest
12+
import jakarta.inject.Inject
13+
import spock.lang.Specification
14+
15+
@MicronautTest
16+
@Property(name = "spec.name", value = "Http2Spec")
17+
@Property(name = "micronaut.server.http-version", value = "HTTP_2_0")
18+
@Property(name = "micronaut.server.ssl.build-self-signed", value = "true")
19+
@Property(name = "micronaut.server.ssl.enabled", value = "true")
20+
@Property(name = "micronaut.http.client.ssl.insecure-trust-all-certificates", value = "true")
21+
@Property(name = "micronaut.server.max-request-size", value = "16384")
22+
class Http2Spec extends Specification {
23+
24+
@Inject
25+
@Client(value = "/", alpnModes = HttpVersionSelection.ALPN_HTTP_2)
26+
HttpClient client
27+
28+
def "test http2"() {
29+
when:
30+
def response = client.toBlocking().retrieve(HttpRequest.GET("/http2"))
31+
32+
then:
33+
response == "hello"
34+
}
35+
36+
def "test http2 post long string"() {
37+
when:
38+
// Body must be >8096 bytes, according to io.netty.handler.codec.http.multipart.HttpPostBodyUtil#chunkSize
39+
def body = Map.of("q", "a" * 8097)
40+
41+
def response = client.toBlocking().retrieve(HttpRequest.POST("/http2", body)
42+
.contentType(MediaType.APPLICATION_FORM_URLENCODED_TYPE))
43+
44+
then:
45+
response == "hello"
46+
}
47+
48+
49+
@Controller("/http2")
50+
@Requires(property = "spec.name", value = "Http2Spec")
51+
static class SpecController {
52+
53+
@Get
54+
@Produces(MediaType.TEXT_PLAIN)
55+
String get() {
56+
"hello"
57+
}
58+
59+
@Post
60+
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
61+
@Produces(MediaType.TEXT_PLAIN)
62+
String post(@Body Map<String, String> ignored) {
63+
"hello"
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)