|
3 | 3 | import com.google.protobuf.ByteString; |
4 | 4 | import com.xiaoyu.rpc.common.vo.RpcResponse; |
5 | 5 | import com.xiaoyu.rpc.core.client.NettyRpcClientHandler; |
| 6 | +import com.xiaoyu.rpc.core.client.RpcStreamResponseHandler; |
6 | 7 | import io.netty.buffer.ByteBuf; |
7 | 8 | import io.netty.buffer.Unpooled; |
8 | 9 | import io.netty.channel.embedded.EmbeddedChannel; |
| 10 | +import io.netty.handler.codec.CorruptedFrameException; |
9 | 11 | import io.netty.handler.codec.http2.DefaultHttp2DataFrame; |
| 12 | +import io.netty.handler.codec.http2.DefaultHttp2Headers; |
10 | 13 | import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; |
11 | | -import io.netty.handler.codec.http2.Http2Headers; |
12 | 14 | import org.junit.jupiter.api.DisplayName; |
13 | 15 | import org.junit.jupiter.api.Test; |
14 | 16 |
|
15 | 17 | import java.util.concurrent.CompletableFuture; |
16 | 18 | import java.util.concurrent.ExecutionException; |
| 19 | +import java.util.concurrent.TimeUnit; |
17 | 20 |
|
18 | 21 | import static org.junit.jupiter.api.Assertions.*; |
19 | 22 |
|
20 | 23 | @DisplayName("gRPC 客户端响应处理器测试") |
21 | 24 | class GrpcClientResponseHandlerTest { |
22 | 25 |
|
23 | 26 | @Test |
24 | | - @DisplayName("应把 DataFrame 解码为 RpcResponse 并完成 future") |
| 27 | + @DisplayName("完整消息加成功状态尾帧才完成 future") |
25 | 28 | void testDecodeDataFrameToRpcResponse() throws Exception { |
26 | 29 | NettyRpcClientHandler clientHandler = new NettyRpcClientHandler(); |
27 | | - EmbeddedChannel channel = new EmbeddedChannel( |
28 | | - new GrpcClientResponseHandler(clientHandler, "req-1"), |
29 | | - clientHandler); |
30 | | - |
| 30 | + EmbeddedChannel channel = channel(clientHandler, "req-1"); |
31 | 31 | CompletableFuture<Object> future = new CompletableFuture<>(); |
32 | 32 | clientHandler.addFuture("req-1", future); |
33 | | - |
34 | | - RpcResponse response = RpcResponse.newBuilder() |
35 | | - .setRequestId("req-1") |
36 | | - .setMessage("Success") |
37 | | - .setData(ByteString.copyFromUtf8("ok")) |
38 | | - .build(); |
39 | | - |
40 | | - byte[] payload = response.toByteArray(); |
41 | | - ByteBuf buf = Unpooled.buffer(); |
42 | | - buf.writeByte(0); |
43 | | - buf.writeInt(payload.length); |
44 | | - buf.writeBytes(payload); |
45 | | - |
46 | | - channel.writeInbound(new DefaultHttp2DataFrame(buf, true)); |
47 | | - |
48 | | - assertTrue(future.isDone(), "Future should be completed"); |
49 | | - Object result = future.get(); |
50 | | - assertInstanceOf(RpcResponse.class, result); |
51 | | - assertEquals("req-1", ((RpcResponse) result).getRequestId()); |
52 | | - assertEquals("Success", ((RpcResponse) result).getMessage()); |
| 33 | + RpcResponse response = response("req-1"); |
| 34 | + try { |
| 35 | + channel.writeInbound(new DefaultHttp2HeadersFrame(new DefaultHttp2Headers() |
| 36 | + .status("200").set("content-type", "application/grpc"), false)); |
| 37 | + channel.writeInbound(new DefaultHttp2DataFrame(framed(response), false)); |
| 38 | + assertFalse(future.isDone(), "A DATA frame does not establish gRPC success"); |
| 39 | + channel.writeInbound(new DefaultHttp2HeadersFrame(new DefaultHttp2Headers() |
| 40 | + .set("grpc-status", "0"), true)); |
| 41 | + assertEquals(response, future.get(1, TimeUnit.SECONDS)); |
| 42 | + } finally { |
| 43 | + channel.finishAndReleaseAll(); |
| 44 | + } |
53 | 45 | } |
54 | 46 |
|
55 | 47 | @Test |
56 | 48 | @DisplayName("收到 grpc-status 非 0 时应异常完成 future") |
57 | 49 | void testFailFutureOnGrpcErrorStatus() { |
58 | 50 | NettyRpcClientHandler clientHandler = new NettyRpcClientHandler(); |
59 | | - EmbeddedChannel channel = new EmbeddedChannel( |
60 | | - new GrpcClientResponseHandler(clientHandler, "req-2"), |
61 | | - clientHandler); |
62 | | - |
| 51 | + EmbeddedChannel channel = channel(clientHandler, "req-2"); |
63 | 52 | CompletableFuture<Object> future = new CompletableFuture<>(); |
64 | 53 | clientHandler.addFuture("req-2", future); |
| 54 | + try { |
| 55 | + channel.writeInbound(new DefaultHttp2HeadersFrame(new DefaultHttp2Headers() |
| 56 | + .status("200").set("grpc-status", "13").set("grpc-message", "internal"), true)); |
| 57 | + assertTrue(future.isCompletedExceptionally()); |
| 58 | + ExecutionException error = assertThrows(ExecutionException.class, |
| 59 | + () -> future.get(1, TimeUnit.SECONDS)); |
| 60 | + assertTrue(error.getCause().getMessage().contains("gRPC status=13")); |
| 61 | + } finally { |
| 62 | + channel.finishAndReleaseAll(); |
| 63 | + } |
| 64 | + } |
65 | 65 |
|
66 | | - Http2Headers trailers = new io.netty.handler.codec.http2.DefaultHttp2Headers() |
67 | | - .set("grpc-status", "13") |
68 | | - .set("grpc-message", "internal"); |
69 | | - channel.writeInbound(new DefaultHttp2HeadersFrame(trailers, true)); |
| 66 | + @Test |
| 67 | + @DisplayName("DATA 直接结束且缺少状态尾帧时必须失败") |
| 68 | + void testMissingTrailersCannotBeSuccess() { |
| 69 | + NettyRpcClientHandler clientHandler = new NettyRpcClientHandler(); |
| 70 | + EmbeddedChannel channel = channel(clientHandler, "req-3"); |
| 71 | + CompletableFuture<Object> future = new CompletableFuture<>(); |
| 72 | + clientHandler.addFuture("req-3", future); |
| 73 | + try { |
| 74 | + channel.writeInbound(new DefaultHttp2DataFrame(framed(response("req-3")), true)); |
| 75 | + ExecutionException error = assertThrows(ExecutionException.class, |
| 76 | + () -> future.get(1, TimeUnit.SECONDS)); |
| 77 | + assertInstanceOf(CorruptedFrameException.class, error.getCause()); |
| 78 | + } finally { |
| 79 | + channel.finishAndReleaseAll(); |
| 80 | + } |
| 81 | + } |
| 82 | + |
| 83 | + private static EmbeddedChannel channel(NettyRpcClientHandler handler, String requestId) { |
| 84 | + return new EmbeddedChannel(new GrpcClientResponseHandler(handler, requestId), |
| 85 | + new RpcStreamResponseHandler(handler, requestId)); |
| 86 | + } |
| 87 | + |
| 88 | + private static RpcResponse response(String requestId) { |
| 89 | + return RpcResponse.newBuilder().setRequestId(requestId).setMessage("Success") |
| 90 | + .setData(ByteString.copyFromUtf8("ok")).build(); |
| 91 | + } |
70 | 92 |
|
71 | | - assertTrue(future.isCompletedExceptionally(), "Future should be completed exceptionally"); |
72 | | - ExecutionException ex = assertThrows(ExecutionException.class, future::get); |
73 | | - String message = ex.getCause().getMessage(); |
74 | | - assertNotNull(message, "Error message should not be null"); |
75 | | - assertTrue(message.toLowerCase().contains("grpc"), "Error message should include grpc details"); |
| 93 | + private static ByteBuf framed(RpcResponse response) { |
| 94 | + byte[] payload = response.toByteArray(); |
| 95 | + return Unpooled.buffer(payload.length + 5).writeByte(0).writeInt(payload.length).writeBytes(payload); |
76 | 96 | } |
77 | 97 | } |
0 commit comments