Skip to content

Commit 1586dfa

Browse files
feat: http/http2 support transparent info (#15)
* feat: http/http2 support transparent info * fix: optimize test * fix: transparent info use bytes
1 parent aec6229 commit 1586dfa

6 files changed

Lines changed: 336 additions & 19 deletions

File tree

trpc-proto/trpc-proto-http/src/main/java/com/tencent/trpc/proto/http/client/Http2ConsumerInvoker.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
* Tencent is pleased to support the open source community by making tRPC available.
33
*
4-
* Copyright (C) 2023 THL A29 Limited, a Tencent company.
4+
* Copyright (C) 2023 THL A29 Limited, a Tencent company.
55
* All rights reserved.
66
*
77
* If you have downloaded a copy of the tRPC source code from Tencent,
@@ -15,6 +15,7 @@
1515
import static com.tencent.trpc.core.common.Constants.DEFAULT_CLIENT_REQUEST_TIMEOUT_MS;
1616
import static com.tencent.trpc.proto.http.common.HttpConstants.CONNECTION_REQUEST_TIMEOUT;
1717

18+
import autovalue.shaded.com.google.common.common.base.Objects;
1819
import com.tencent.trpc.core.common.config.BackendConfig;
1920
import com.tencent.trpc.core.common.config.ConsumerConfig;
2021
import com.tencent.trpc.core.common.config.ProtocolConfig;
@@ -25,6 +26,9 @@
2526
import com.tencent.trpc.core.rpc.Response;
2627
import com.tencent.trpc.core.utils.RpcUtils;
2728
import com.tencent.trpc.proto.http.common.HttpConstants;
29+
import java.nio.charset.StandardCharsets;
30+
import java.util.HashMap;
31+
import java.util.Map;
2832
import java.util.concurrent.Future;
2933
import java.util.concurrent.TimeUnit;
3034
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
@@ -89,6 +93,15 @@ private Response handleResponse(Request request, SimpleHttpResponse simpleHttpRe
8993
throw TRpcException
9094
.newBizException(statusCode, simpleHttpResponse.getReasonPhrase());
9195
}
96+
// handle http header
97+
// Parse the data passed through from the server to the client.
98+
// In the tRPC protocol, the value of the attachment is stored and used as a byte array to maintain consistency.
99+
Map<String, Object> respAttachments = new HashMap<>();
100+
for (Header header : simpleHttpResponse.getHeaders()) {
101+
String name = header.getName();
102+
String value = header.getValue();
103+
respAttachments.put(name, value.getBytes(StandardCharsets.UTF_8));
104+
}
92105

93106
Header contentLengthHdr = simpleHttpResponse.getFirstHeader(HttpHeaders.CONTENT_LENGTH);
94107
if (contentLengthHdr != null) {
@@ -97,7 +110,9 @@ private Response handleResponse(Request request, SimpleHttpResponse simpleHttpRe
97110
// but other HTTP implementations may not return it. Therefore,
98111
// no strict validation is performed here.
99112
if (contentLength == 0) {
100-
return RpcUtils.newResponse(request, null, null);
113+
Response response = RpcUtils.newResponse(request, null, null);
114+
response.setAttachments(respAttachments);
115+
return response;
101116
}
102117
}
103118

@@ -106,7 +121,9 @@ private Response handleResponse(Request request, SimpleHttpResponse simpleHttpRe
106121
request.getInvocation().getRpcMethodInfo().getActualReturnType(),
107122
simpleHttpResponse.getBodyText());
108123

109-
return RpcUtils.newResponse(request, value, null);
124+
Response response = RpcUtils.newResponse(request, value, null);
125+
response.setAttachments(respAttachments);
126+
return response;
110127
}
111128

112129
/**
@@ -187,6 +204,17 @@ private SimpleHttpRequest buildRequest(Request request, int requestTimeout) thro
187204
if (jsonString != null) {
188205
simpleHttpRequest.setBody(jsonString, ContentType.APPLICATION_JSON);
189206
}
207+
// Set custom business headers, consistent with the TRPC protocol, only process String and byte[]
208+
request.getAttachments().forEach((k, v) -> {
209+
if (Objects.equal(k, HttpHeaders.TRANSFER_ENCODING) || Objects.equal(k, HttpHeaders.CONTENT_LENGTH)) {
210+
return;
211+
}
212+
if (v instanceof String) {
213+
simpleHttpRequest.setHeader(k, String.valueOf(v));
214+
} else if (v instanceof byte[]) {
215+
simpleHttpRequest.setHeader(k, new String((byte[]) v));
216+
}
217+
});
190218
return simpleHttpRequest;
191219
}
192220

trpc-proto/trpc-proto-http/src/main/java/com/tencent/trpc/proto/http/client/HttpConsumerInvoker.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
* Tencent is pleased to support the open source community by making tRPC available.
33
*
4-
* Copyright (C) 2023 THL A29 Limited, a Tencent company.
4+
* Copyright (C) 2023 THL A29 Limited, a Tencent company.
55
* All rights reserved.
66
*
77
* If you have downloaded a copy of the tRPC source code from Tencent,
@@ -27,9 +27,12 @@
2727
import com.tencent.trpc.proto.http.common.HttpConstants;
2828
import java.io.InputStream;
2929
import java.nio.charset.StandardCharsets;
30+
import java.util.HashMap;
31+
import java.util.Map;
3032
import java.util.Objects;
3133
import org.apache.commons.io.IOUtils;
3234
import org.apache.http.Header;
35+
import org.apache.http.HeaderElement;
3336
import org.apache.http.HttpHeaders;
3437
import org.apache.http.HttpRequest;
3538
import org.apache.http.HttpStatus;
@@ -84,24 +87,39 @@ private Response handleResponse(Request request, CloseableHttpResponse httpRespo
8487
throw TRpcException.newBizException(statusCode,
8588
httpResponse.getStatusLine().getReasonPhrase());
8689
}
90+
// handle http header
91+
// Parse the data passed through from the server to the client.
92+
// In the tRPC protocol, the value of the attachment is stored and used as a byte array to maintain consistency.
93+
Map<String, Object> respAttachments = new HashMap<>();
94+
for (Header header : httpResponse.getAllHeaders()) {
95+
String name = header.getName();
96+
for (HeaderElement element : header.getElements()) {
97+
String value = element.getName();
98+
respAttachments.put(name, value.getBytes(StandardCharsets.UTF_8));
99+
}
100+
}
87101

88102
Header contentLengthHdr = httpResponse.getFirstHeader(HttpHeaders.CONTENT_LENGTH);
103+
89104
if (contentLengthHdr != null) {
90105
int contentLength = Integer.parseInt(contentLengthHdr.getValue().trim());
91106
// NOTE: By default, the HTTP implementation must return the content length.
92107
// However, other HTTP implementations may not return the content length,
93108
// so strong validation is not performed here.
94109
if (contentLength == 0) {
95-
return RpcUtils.newResponse(request, null, null);
110+
Response response = RpcUtils.newResponse(request, null, null);
111+
response.setAttachments(respAttachments);
112+
return response;
96113
}
97114
}
98-
99115
// Decoded response result.
100116
InputStream in = httpResponse.getEntity().getContent();
101117
String decodeIn = IOUtils.toString(in, StandardCharsets.UTF_8);
102118
Object value = decodeFromJson(
103119
request.getInvocation().getRpcMethodInfo().getActualReturnType(), decodeIn);
104-
return RpcUtils.newResponse(request, value, null);
120+
Response response = RpcUtils.newResponse(request, value, null);
121+
response.setAttachments(respAttachments);
122+
return response;
105123
}
106124

107125
/**

trpc-proto/trpc-proto-http/src/main/java/com/tencent/trpc/proto/http/common/HttpCodec.java

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
* Tencent is pleased to support the open source community by making tRPC available.
33
*
4-
* Copyright (C) 2023 THL A29 Limited, a Tencent company.
4+
* Copyright (C) 2023 THL A29 Limited, a Tencent company.
55
* All rights reserved.
66
*
77
* If you have downloaded a copy of the tRPC source code from Tencent,
@@ -17,27 +17,18 @@
1717
import com.tencent.trpc.core.serialization.spi.Serialization;
1818
import com.tencent.trpc.core.serialization.support.JSONSerialization;
1919
import com.tencent.trpc.core.serialization.support.PBSerialization;
20-
import com.tencent.trpc.core.utils.JsonUtils;
2120
import com.tencent.trpc.core.utils.ProtoJsonConverter;
2221
import com.tencent.trpc.proto.http.util.StreamUtils;
23-
import org.apache.commons.beanutils.BeanUtils;
24-
import org.apache.http.HttpHeaders;
25-
import javax.servlet.ServletInputStream;
26-
import javax.servlet.ServletOutputStream;
27-
import javax.servlet.http.HttpServletRequest;
28-
import javax.servlet.http.HttpServletResponse;
29-
import java.io.ByteArrayOutputStream;
3022
import java.io.IOException;
31-
import java.lang.reflect.InvocationTargetException;
3223
import java.lang.reflect.Method;
3324
import java.util.Enumeration;
3425
import java.util.HashMap;
3526
import java.util.Map;
3627
import javax.servlet.ServletOutputStream;
3728
import javax.servlet.http.HttpServletRequest;
3829
import javax.servlet.http.HttpServletResponse;
30+
import org.apache.commons.beanutils.BeanUtils;
3931
import org.apache.http.HttpHeaders;
40-
import org.springframework.cglib.beans.BeanMap;
4132

4233
/**
4334
* HTTP encoding and decoding utility class, also used in the Spring MVC module.
@@ -216,7 +207,8 @@ public void writeHttpResponse(HttpServletResponse response, Response result) thr
216207
data = jsonSerialization.serialize(value);
217208
}
218209
}
219-
210+
// Encapsulate the data passed through from the server to the client in the HTTP response header.
211+
fillResponseHeaderWithAttachments(response, result);
220212
if (data != null) {
221213
response.setHeader(HttpHeaders.CONTENT_TYPE, HttpConstants.CONTENT_TYPE_JSON);
222214
response.setContentLength(data.length);
@@ -225,4 +217,19 @@ public void writeHttpResponse(HttpServletResponse response, Response result) thr
225217
}
226218
}
227219

220+
private void fillResponseHeaderWithAttachments(HttpServletResponse response, Response result) throws IOException {
221+
Map<String, Object> attachments = result.getAttachments();
222+
if (attachments == null || attachments.isEmpty()) {
223+
return;
224+
}
225+
// Set custom business headers, consistent with the TRPC protocol, only process String and byte[]
226+
attachments.forEach((name, value) -> {
227+
if (value instanceof String) {
228+
response.setHeader(name, String.valueOf(value));
229+
} else if (value instanceof byte[]) {
230+
response.setHeader(name, new String((byte[]) value));
231+
}
232+
});
233+
}
234+
228235
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*
2+
* Tencent is pleased to support the open source community by making tRPC available.
3+
*
4+
* Copyright (C) 2023 THL A29 Limited, a Tencent company.
5+
* All rights reserved.
6+
*
7+
* If you have downloaded a copy of the tRPC source code from Tencent,
8+
* please note that tRPC source code is licensed under the Apache 2.0 License,
9+
* A copy of the Apache 2.0 License can be found in the LICENSE file.
10+
*/
11+
12+
package com.tencent.trpc.proto.http.common;
13+
14+
import static com.tencent.trpc.proto.http.common.HttpConstants.HTTP_SCHEME;
15+
import static com.tencent.trpc.proto.http.constant.Constant.TEST_BYTES_REQ_KEY;
16+
import static com.tencent.trpc.proto.http.constant.Constant.TEST_BYTES_REQ_VALUE;
17+
import static com.tencent.trpc.proto.http.constant.Constant.TEST_BYTES_RSP_KEY;
18+
import static com.tencent.trpc.proto.http.constant.Constant.TEST_BYTES_RSP_VALUE;
19+
import static com.tencent.trpc.proto.http.constant.Constant.TEST_MESSAGE;
20+
import static com.tencent.trpc.proto.http.constant.Constant.TEST_RSP_MESSAGE;
21+
import static com.tencent.trpc.proto.http.constant.Constant.TEST_STRING_REQ_KEY;
22+
import static com.tencent.trpc.proto.http.constant.Constant.TEST_STRING_REQ_VALUE;
23+
import static com.tencent.trpc.proto.http.constant.Constant.TEST_STRING_RSP_KEY;
24+
import static com.tencent.trpc.proto.http.constant.Constant.TEST_STRING_RSP_VALUE;
25+
import static com.tencent.trpc.transport.http.common.Constants.HTTP2_SCHEME;
26+
27+
import com.tencent.trpc.core.common.ConfigManager;
28+
import com.tencent.trpc.core.common.config.BackendConfig;
29+
import com.tencent.trpc.core.common.config.ConsumerConfig;
30+
import com.tencent.trpc.core.common.config.ProviderConfig;
31+
import com.tencent.trpc.core.common.config.ServerConfig;
32+
import com.tencent.trpc.core.common.config.ServiceConfig;
33+
import com.tencent.trpc.core.rpc.RpcClientContext;
34+
import com.tencent.trpc.core.utils.NetUtils;
35+
import java.nio.charset.StandardCharsets;
36+
import java.util.HashMap;
37+
import org.apache.http.HttpHeaders;
38+
import org.junit.AfterClass;
39+
import org.junit.Assert;
40+
import org.junit.BeforeClass;
41+
import org.junit.Test;
42+
import tests.service.GreeterService;
43+
import tests.service.HelloRequestProtocol.HelloRequest;
44+
import tests.service.HelloRequestProtocol.HelloResponse;
45+
import tests.service.impl1.GreeterServiceImpl2;
46+
47+
public class HttpTransparentInfoTest {
48+
49+
private static ServerConfig serverConfig;
50+
51+
private static int TRPC_JAVA_TEST_HTTP2_PORT;
52+
53+
private static int TRPC_JAVA_TEST_HTTP_PORT;
54+
55+
@BeforeClass
56+
public static void startHttpServer() {
57+
ConfigManager.stopTest();
58+
ConfigManager.startTest();
59+
60+
ProviderConfig<GreeterService> providerConfig = new ProviderConfig<>();
61+
providerConfig.setServiceInterface(GreeterService.class);
62+
providerConfig.setRef(new GreeterServiceImpl2());
63+
HashMap<String, ServiceConfig> providers = new HashMap<>();
64+
65+
TRPC_JAVA_TEST_HTTP2_PORT = NetUtils.getAvailablePort();
66+
ServiceConfig serviceConfig1 = getServiceConfig(providerConfig, "trpc.java.test.http2",
67+
NetUtils.LOCAL_HOST, TRPC_JAVA_TEST_HTTP2_PORT, HTTP2_SCHEME, "jetty");
68+
providers.put(serviceConfig1.getName(), serviceConfig1);
69+
70+
TRPC_JAVA_TEST_HTTP_PORT = NetUtils.getAvailablePort();
71+
ServiceConfig serviceConfig2 = getServiceConfig(providerConfig, "trpc.java.test.http",
72+
NetUtils.LOCAL_HOST, TRPC_JAVA_TEST_HTTP_PORT, HTTP_SCHEME, "jetty");
73+
providers.put(serviceConfig2.getName(), serviceConfig2);
74+
75+
ServerConfig sc = new ServerConfig();
76+
sc.setServiceMap(providers);
77+
sc.setApp("http-test-app");
78+
sc.setLocalIp("127.0.0.1");
79+
sc.init();
80+
81+
serverConfig = sc;
82+
}
83+
84+
private static ServiceConfig getServiceConfig(ProviderConfig gspc, String name,
85+
String ip, int port, String protocol, String transport) {
86+
ServiceConfig serviceConfig = new ServiceConfig();
87+
serviceConfig.setName(name);
88+
serviceConfig.getProviderConfigs().add(gspc);
89+
serviceConfig.setIp(ip);
90+
serviceConfig.setPort(port);
91+
serviceConfig.setProtocol(protocol);
92+
serviceConfig.setTransporter(transport);
93+
return serviceConfig;
94+
}
95+
96+
@AfterClass
97+
public static void stopHttpServer() {
98+
ConfigManager.stopTest();
99+
if (serverConfig != null) {
100+
serverConfig.stop();
101+
serverConfig = null;
102+
}
103+
}
104+
105+
private static HelloRequest createPbRequest(String msg) {
106+
HelloRequest.Builder builder = HelloRequest.newBuilder();
107+
builder.setMessage(msg);
108+
return builder.build();
109+
}
110+
111+
@Test
112+
public void testHttp2RpcClient() {
113+
BackendConfig backendConfig = new BackendConfig();
114+
ConsumerConfig<GreeterService> consumerConfig = new ConsumerConfig<>();
115+
backendConfig.setName("serviceId");
116+
backendConfig.setRequestTimeout(10000);
117+
consumerConfig.setServiceInterface(GreeterService.class);
118+
consumerConfig.setBackendConfig(backendConfig);
119+
backendConfig.setNamingUrl("ip://127.0.0.1:" + TRPC_JAVA_TEST_HTTP2_PORT);
120+
backendConfig.setKeepAlive(false);
121+
backendConfig.setConnsPerAddr(4);
122+
backendConfig.setProtocol(HTTP2_SCHEME);
123+
try {
124+
GreeterService proxy = consumerConfig.getProxy();
125+
126+
RpcClientContext context = new RpcClientContext();
127+
// client tran info to server
128+
context.getReqAttachMap().put(TEST_STRING_REQ_KEY, TEST_STRING_REQ_VALUE);
129+
context.getReqAttachMap().put(TEST_BYTES_REQ_KEY, TEST_BYTES_REQ_VALUE);
130+
context.getReqAttachMap().put(HttpHeaders.CONTENT_LENGTH, TEST_MESSAGE.length());
131+
132+
HelloResponse helloResponse = proxy.sayHello(context, createPbRequest(TEST_MESSAGE));
133+
// get server tran info
134+
byte[] bytesRspValue = (byte[]) context.getRspAttachMap().get(TEST_BYTES_RSP_KEY);
135+
Assert.assertArrayEquals(bytesRspValue, TEST_BYTES_RSP_VALUE);
136+
137+
byte[] stringRspValue = (byte[]) context.getRspAttachMap().get(TEST_STRING_RSP_KEY);
138+
Assert.assertEquals(new String(stringRspValue, StandardCharsets.UTF_8), TEST_STRING_RSP_VALUE);
139+
140+
Assert.assertNotNull(helloResponse);
141+
String rspMessage = helloResponse.getMessage();
142+
Assert.assertEquals(rspMessage, TEST_RSP_MESSAGE);
143+
} finally {
144+
backendConfig.stop();
145+
}
146+
}
147+
148+
@Test
149+
public void testHttpRpcClient() {
150+
BackendConfig backendConfig = new BackendConfig();
151+
ConsumerConfig<GreeterService> consumerConfig = new ConsumerConfig<>();
152+
backendConfig.setName("serviceId");
153+
backendConfig.setRequestTimeout(10000);
154+
consumerConfig.setServiceInterface(GreeterService.class);
155+
consumerConfig.setBackendConfig(backendConfig);
156+
backendConfig.setNamingUrl("ip://127.0.0.1:" + TRPC_JAVA_TEST_HTTP_PORT);
157+
backendConfig.setKeepAlive(false);
158+
backendConfig.setConnsPerAddr(4);
159+
backendConfig.setProtocol(HTTP_SCHEME);
160+
try {
161+
GreeterService proxy = consumerConfig.getProxy();
162+
163+
RpcClientContext context = new RpcClientContext();
164+
context.getReqAttachMap().put(TEST_STRING_REQ_KEY, TEST_STRING_REQ_VALUE);
165+
context.getReqAttachMap().put(TEST_BYTES_REQ_KEY, TEST_BYTES_REQ_VALUE);
166+
167+
HelloResponse helloResponse = proxy.sayHello(context, createPbRequest(TEST_MESSAGE));
168+
// get server tran info
169+
byte[] bytesRspValue = (byte[]) context.getRspAttachMap().get(TEST_BYTES_RSP_KEY);
170+
Assert.assertArrayEquals(bytesRspValue, TEST_BYTES_RSP_VALUE);
171+
172+
byte[] stringRspValue = (byte[]) context.getRspAttachMap().get(TEST_STRING_RSP_KEY);
173+
Assert.assertEquals(new String(stringRspValue, StandardCharsets.UTF_8), TEST_STRING_RSP_VALUE);
174+
175+
Assert.assertNotNull(helloResponse);
176+
String rspMessage = helloResponse.getMessage();
177+
Assert.assertEquals(rspMessage, TEST_RSP_MESSAGE);
178+
} finally {
179+
backendConfig.stop();
180+
}
181+
}
182+
}

0 commit comments

Comments
 (0)