Rest 协议

本文将介绍 Dubbo 的 Rest 协议。

注意

从 Dubbo 3.3 版本开始,Rest 协议已移至 Extensions 库,由 Triple 协议来对 Rest 提供更全面的支持,具体参见 Triple Rest用户手册, 如需继续使用原 Rest 协议,可引入对应 dubbo-spi-extensions 库依赖

更加轻量,具有dubbo风格的rest,微服务体系互通(Springcloud Alibaba)

1.注解解析

2.报文编解码

3.restClient

4.restServer(netty)

支持程度:

content-type text json xml form(后续会扩展)

注解

param,header,body,pathvariable (spring mvc & resteasy)

Http 协议报文

1. POST /test/path?  HTTP/1.1
2. Host: localhost:8080
3. Connection: keep-alive
4. Content-type: application/json

7. {"name":"dubbo","age":10,"address":"hangzhou"}

dubbo http(header)

1. // service key header
2. path: com.demo.TestInterface
3. group: demo
4. port: 80
5. version: 1.0.0

7. // 保证长连接
8. Keep-Alive,Connection: keep-alive
9. Keep-alive: 60

11. // RPCContext Attachment
12. userId: 123456

目前支持粒度:

数据位置 content-type spring注解 resteasy注解
body 无要求 ReuqestBody 无注解即为body
querystring(?test=demo) 无要求 RequestParam QueryParam
header 无要求 RequestHeader PathParam
form application/x-www-form-urlencoded RequestParam ReuqestBody FormParam
path 无要求 PathVariable PathParam
method 无要求 PostMapping GetMapping GET POST
url PostMapping GetMapping path属性 Path
content-type PostMapping GetMapping consumers属性 Consumers
Accept PostMapping GetMapping produces属性 Produces

rest注解解析(ServiceRestMetadataResolver)

1. JAXRSServiceRestMetadataResolver

3. SpringMvcServiceRestMetadataResolver

ServiceRestMetadata

1. public class ServiceRestMetadata implements Serializable {

3. private String serviceInterface; // com.demo.TestInterface

5. private String version;// 1.0.0

7. private String group;// demo

9. private Set<RestMethodMetadata> meta;// method 元信息

11. private int port;// 端口 for provider service key

13. private boolean consumer;// consumer 标志

15. /**
16. * make a distinction between mvc & resteasy
17. */
18. private Class codeStyle;//

20. /**
21. *  for provider
22. */
23. private Map<PathMatcher, RestMethodMetadata> pathToServiceMap;

25. /**
26. * for consumer
27. */
28. private Map<String, Map<ParameterTypesComparator, RestMethodMetadata>> methodToServiceMa

RestMethodMetadata

1. public class RestMethodMetadata implements Serializable {

3. private MethodDefinition method; // method 定义信息(name ,pramType,returnType)

5. private RequestMetadata request;// 请求元信息

7. private Integer urlIndex;

9. private Integer bodyIndex;

11. private Integer headerMapIndex;

13. private String bodyType;

15. private Map<Integer, Collection<String>> indexToName;

17. private List<String> formParams;

19. private Map<Integer, Boolean> indexToEncoded;

21. private ServiceRestMetadata serviceRestMetadata;

23. private List<ArgInfo> argInfos;

25. private Method reflectMethod;

27. /**
28. *  make a distinction between mvc & resteasy
29. */
30. private Class codeStyle;

ArgInfo

1. public class ArgInfo {
2. /**
3. * method arg index 0,1,2,3
4. */
5. private int index;
6. /**
7. * method annotation name or name
8. */
9. private String annotationNameAttribute;

11. /**
12. * param annotation type
13. */
14. private Class paramAnnotationType;

16. /**
17. * param Type
18. */
19. private Class paramType;

21. /**
22. * param name
23. */
24. private String paramName;

26. /**
27. * url split("/") String[n]  index
28. */
29. private int urlSplitIndex;

31. private Object defaultValue;

33. private boolean formContentType;

RequestMeatadata

1. public class RequestMetadata implements Serializable {

3. private static final long serialVersionUID = -240099840085329958L;

5. private String method;// 请求method

7. private String path;// 请求url

10. private Map<String, List<String>> params // param参数?拼接

12. private Map<String, List<String>> headers// header;

14. private Set<String> consumes // content-type;

16. private Set<String> produces // Accept;

Consumer 代码:

refer:

1. @Override
2. protected <T> Invoker<T> protocolBindingRefer(final Class<T> type, final URL url) throws RpcException {

4. // restClient spi创建
5. ReferenceCountedClient<? extends RestClient> refClient =
6. clients.computeIfAbsent(url.getAddress(), key -> createReferenceCountedClient(url, clients));

8. refClient.retain();

10. // resolve metadata
11. Map<String, Map<ParameterTypesComparator, RestMethodMetadata>> metadataMap = MetadataResolver.resolveConsumerServiceMetadata(type, url);

13. ReferenceCountedClient<? extends RestClient> finalRefClient = refClient;
14. Invoker<T> invoker = new AbstractInvoker<T>(type, url, new String[]{INTERFACE_KEY, GROUP_KEY, TOKEN_KEY}) {
15. @Override
16. protected Result doInvoke(Invocation invocation) {
17. try {
18. // 获取 method的元信息
19. RestMethodMetadata restMethodMetadata = metadataMap.get(invocation.getMethodName()).get(ParameterTypesComparator.getInstance(invocation.getParameterTypes()));

21. RequestTemplate requestTemplate = new RequestTemplate(invocation, restMethodMetadata.getRequest().getMethod(), url.getAddress(), getContextPath(url));

23. HttpConnectionCreateContext httpConnectionCreateContext = new HttpConnectionCreateContext();
24. // TODO  dynamic load config
25. httpConnectionCreateContext.setConnectionConfig(new HttpConnectionConfig());
26. httpConnectionCreateContext.setRequestTemplate(requestTemplate);
27. httpConnectionCreateContext.setRestMethodMetadata(restMethodMetadata);
28. httpConnectionCreateContext.setInvocation(invocation);
29. httpConnectionCreateContext.setUrl(url);

31. // http 信息构建拦截器
32. for (HttpConnectionPreBuildIntercept intercept : httpConnectionPreBuildIntercepts) {
33. intercept.intercept(httpConnectionCreateContext);
34. }

37. CompletableFuture<RestResult> future = finalRefClient.getClient().send(requestTemplate);
38. CompletableFuture<AppResponse> responseFuture = new CompletableFuture<>();
39. AsyncRpcResult asyncRpcResult = new AsyncRpcResult(responseFuture, invocation);
40. // response 处理
41. future.whenComplete((r, t) -> {
42. if (t != null) {
43. responseFuture.completeExceptionally(t);
44. } else {
45. AppResponse appResponse = new AppResponse();
46. try {
47. int responseCode = r.getResponseCode();
48. MediaType mediaType = MediaType.TEXT_PLAIN;

50. if (400 < responseCode && responseCode < 500) {
51. throw new HttpClientException(r.getMessage());
52. } else if (responseCode >= 500) {
53. throw new RemoteServerInternalException(r.getMessage());
54. } else if (responseCode < 400) {
55. mediaType = MediaTypeUtil.convertMediaType(r.getContentType());
56. }

59. Object value = HttpMessageCodecManager.httpMessageDecode(r.getBody(),
60. restMethodMetadata.getReflectMethod().getReturnType(), mediaType);
61. appResponse.setValue(value);
62. Map<String, String> headers = r.headers()
63. .entrySet()
64. .stream()
65. .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
66. appResponse.setAttachments(headers);
67. responseFuture.complete(appResponse);
68. } catch (Exception e) {
69. responseFuture.completeExceptionally(e);
70. }
71. }
72. });
73. return asyncRpcResult;
74. } catch (RpcException e) {
75. if (e.getCode() == RpcException.UNKNOWN_EXCEPTION) {
76. e.setCode(getErrorCode(e.getCause()));
77. }
78. throw e;
79. }
80. }

82. @Override
83. public void destroy() {
84. super.destroy();
85. invokers.remove(this);
86. destroyInternal(url);
87. }
88. };
89. invokers.add(invoker);
90. return invoker;

provider 代码:

export:

1. public <T> Exporter<T> export(final Invoker<T> invoker) throws RpcException {
2. URL url = invoker.getUrl();
3. final String uri = serviceKey(url);
4. Exporter<T> exporter = (Exporter<T>) exporterMap.get(uri);
5. if (exporter != null) {
6. // When modifying the configuration through override, you need to re-expose the newly modified service.
7. if (Objects.equals(exporter.getInvoker().getUrl(), invoker.getUrl())) {
8. return exporter;
9. }
10. }

13. // TODO  addAll metadataMap to RPCInvocationBuilder metadataMap
14. Map<PathMatcher, RestMethodMetadata> metadataMap = MetadataResolver.resolveProviderServiceMetadata(url.getServiceModel().getProxyObject().getClass(),url);

16. PathAndInvokerMapper.addPathAndInvoker(metadataMap, invoker);

19. final Runnable runnable = doExport(proxyFactory.getProxy(invoker, true), invoker.getInterface(), invoker.getUrl());
20. exporter = new AbstractExporter<T>(invoker) {
21. @Override
22. public void afterUnExport() {
23. exporterMap.remove(uri);
24. if (runnable != null) {
25. try {
26. runnable.run();
27. } catch (Throwable t) {
28. logger.warn(PROTOCOL_UNSUPPORTED, "", "", t.getMessage(), t);
29. }
30. }
31. }
32. };
33. exporterMap.put(uri, exporter);
34. return exporter;
35. }

RestHandler

1. private class RestHandler implements HttpHandler<HttpServletRequest, HttpServletResponse> {

3. @Override
4. public void handle(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException {
5. // 有servlet reuqest 和nettyRequest
6. RequestFacade request = RequestFacadeFactory.createRequestFacade(servletRequest);
7. RpcContext.getServiceContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort());
8. //            dispatcher.service(request, servletResponse);

10. Pair<RpcInvocation, Invoker> build = null;
11. try {
12. // 根据请求信息创建 RPCInvocation
13. build = RPCInvocationBuilder.build(request, servletRequest, servletResponse);
14. } catch (PathNoFoundException e) {
15. servletResponse.setStatus(404);
16. }

18. Invoker invoker = build.getSecond();

20. Result invoke = invoker.invoke(build.getFirst());

22. // TODO handling  exceptions
23. if (invoke.hasException()) {
24. servletResponse.setStatus(500);
25. } else {

27. try {
28. Object value = invoke.getValue();
29. String accept = request.getHeader(RestConstant.ACCEPT);
30. MediaType mediaType = MediaTypeUtil.convertMediaType(accept);
31. // TODO write response
32. HttpMessageCodecManager.httpMessageEncode(servletResponse.getOutputStream(), value, invoker.getUrl(), mediaType);
33. servletResponse.setStatus(200);
34. } catch (Exception e) {
35. servletResponse.setStatus(500);
36. }

39. }

41. // TODO add Attachment header

44. }
45. }

RPCInvocationBuilder

1. {

4. private static final ParamParserManager paramParser = new ParamParserManager();

7. public static Pair<RpcInvocation, Invoker> build(RequestFacade request, Object servletRequest, Object servletResponse) {

9. // 获取invoker
10. Pair<Invoker, RestMethodMetadata> invokerRestMethodMetadataPair = getRestMethodMetadata(request);

12. RpcInvocation rpcInvocation = createBaseRpcInvocation(request, invokerRestMethodMetadataPair.getSecond());

14. ProviderParseContext parseContext = createParseContext(request, servletRequest, servletResponse, invokerRestMethodMetadataPair.getSecond());
15. // 参数构建
16. Object[] args = paramParser.providerParamParse(parseContext);

18. rpcInvocation.setArguments(args);

20. return Pair.make(rpcInvocation, invokerRestMethodMetadataPair.getFirst());

22. }

24. private static ProviderParseContext createParseContext(RequestFacade request, Object servletRequest, Object servletResponse, RestMethodMetadata restMethodMetadata) {
25. ProviderParseContext parseContext = new ProviderParseContext(request);
26. parseContext.setResponse(servletResponse);
27. parseContext.setRequest(servletRequest);

29. Object[] objects = new Object[restMethodMetadata.getArgInfos().size()];
30. parseContext.setArgs(Arrays.asList(objects));
31. parseContext.setArgInfos(restMethodMetadata.getArgInfos());

34. return parseContext;
35. }

37. private static RpcInvocation createBaseRpcInvocation(RequestFacade request, RestMethodMetadata restMethodMetadata) {
38. RpcInvocation rpcInvocation = new RpcInvocation();

41. int localPort = request.getLocalPort();
42. String localAddr = request.getLocalAddr();
43. int remotePort = request.getRemotePort();
44. String remoteAddr = request.getRemoteAddr();

46. String HOST = request.getHeader(RestConstant.HOST);
47. String GROUP = request.getHeader(RestConstant.GROUP);

49. String PATH = request.getHeader(RestConstant.PATH);
50. String VERSION = request.getHeader(RestConstant.VERSION);

52. String METHOD = restMethodMetadata.getMethod().getName();
53. String[] PARAMETER_TYPES_DESC = restMethodMetadata.getMethod().getParameterTypes();

55. rpcInvocation.setParameterTypes(restMethodMetadata.getReflectMethod().getParameterTypes());

58. rpcInvocation.setMethodName(METHOD);
59. rpcInvocation.setAttachment(RestConstant.GROUP, GROUP);
60. rpcInvocation.setAttachment(RestConstant.METHOD, METHOD);
61. rpcInvocation.setAttachment(RestConstant.PARAMETER_TYPES_DESC, PARAMETER_TYPES_DESC);
62. rpcInvocation.setAttachment(RestConstant.PATH, PATH);
63. rpcInvocation.setAttachment(RestConstant.VERSION, VERSION);
64. rpcInvocation.setAttachment(RestConstant.HOST, HOST);
65. rpcInvocation.setAttachment(RestConstant.REMOTE_ADDR, remoteAddr);
66. rpcInvocation.setAttachment(RestConstant.LOCAL_ADDR, localAddr);
67. rpcInvocation.setAttachment(RestConstant.REMOTE_PORT, remotePort);
68. rpcInvocation.setAttachment(RestConstant.LOCAL_PORT, localPort);

70. Enumeration<String> attachments = request.getHeaders(RestConstant.DUBBO_ATTACHMENT_HEADER);

72. while (attachments != null && attachments.hasMoreElements()) {
73. String s =  attachments.nextElement();

75. String[] split = s.split("=");

77. rpcInvocation.setAttachment(split[0], split[1]);
78. }

81. // TODO set path,version,group and so on
82. return rpcInvocation;
83. }

86. private static Pair<Invoker, RestMethodMetadata> getRestMethodMetadata(RequestFacade request) {
87. String path = request.getRequestURI();
88. String version = request.getHeader(RestConstant.VERSION);
89. String group = request.getHeader(RestConstant.GROUP);
90. int port = request.getIntHeader(RestConstant.REST_PORT);

92. return PathAndInvokerMapper.getRestMethodMetadata(path, version, group, port);
93. }

96. }

编码示例

API

mvc:

1. @RestController()
2. @RequestMapping("/demoService")
3. public interface DemoService {
4. @RequestMapping(value = "/hello", method = RequestMethod.GET)
5. Integer hello(@RequestParam Integer a, @RequestParam Integer b);

7. @RequestMapping(value = "/error", method = RequestMethod.GET)
8. String error();

10. @RequestMapping(value = "/say", method = RequestMethod.POST, consumes = MediaType.TEXT_PLAIN_VALUE)
11. String sayHello(@RequestBody String name);
12. }

resteasy:

1. @Path("/demoService")
2. public interface RestDemoService {
3. @GET
4. @Path("/hello")
5. Integer hello(@QueryParam("a")Integer a,@QueryParam("b") Integer b);

7. @GET
8. @Path("/error")
9. String error();

11. @POST
12. @Path("/say")
13. @Consumes({MediaType.TEXT_PLAIN})
14. String sayHello(String name);

16. boolean isCalled();
17. }

impl(service)

1. @DubboService()
2. public class RestDemoServiceImpl implements RestDemoService {
3. private static Map<String, Object> context;
4. private boolean called;

7. @Override
8. public String sayHello(String name) {
9. called = true;
10. return "Hello, " + name;
11. }

14. public boolean isCalled() {
15. return called;
16. }

18. @Override
19. public Integer hello(Integer a, Integer b) {
20. context = RpcContext.getServerAttachment().getObjectAttachments();
21. return a + b;
22. }

25. @Override
26. public String error() {
27. throw new RuntimeException();
28. }

30. public static Map<String, Object> getAttachments() {
31. return context;
32. }
33. }

流程图

Consumer

image

Provider(RestServer)

image

场景 :

非dubbo体系互通(Springcloud alibaba 互通)

互通条件:

协议 Dubbo SpringCloud Alibaba 互通
通信协议 rest spring web/resteasy  编码风格 集成feignclient,ribbon (spring web 编码风格) 是
triple
dubbo
grpc
hessian
注册中心 zookeeper
nacos 支持 支持 应用级别注册

2.dubbo 双注册

完成应用级别注册,(dubo2-dubbo3 过度),dubbo版本升级

image

image

3.多协议发布

配置:

1. <dubbo:service interface="org.apache.dubbo.samples.DemoService" protocol="dubbo, grpc,rest"/>

4.跨语言

image

5.多协议交互

image

6.协议迁移

image

rest编码风格

Http协议更通用跨语言调用

dubbo rest 对其他http服务 进行调用

其他httpclient 对dubbo rest进行调用

dubbo restServer 可以与其他web服务,浏览器等客户端直接进行http交互

consumer TODOLIST(功能已经初步实现,可以调通解析response)

  1. org/apache/dubbo/rpc/protocol/rest/RestProtocol.java:157 dynamic load config

2.org/apache/dubbo/remoting/http/factory/AbstractHttpClientFactory.java:50 load config HttpClientConfig

3.org/apache/dubbo/rpc/protocol/rest/annotation/metadata/MetadataResolver.java:52 support Dubbo style service

4.org/apache/dubbo/remoting/http/restclient/HttpClientRestClient.java:120 TODO config

5.org/apache/dubbo/remoting/http/restclient/HttpClientRestClient.java:140 TODO close judge

6.org/apache/dubbo/rpc/protocol/rest/message/decode/MultiValueCodec.java:35 TODO java bean get set convert

provider TODOLIST(待实现)

基于netty实现支持http协议的NettyServer

无注解协议定义

官网场景补充

Rest使用说明文档及demo:

最后修改 September 13, 2024: Refactor website structure (#2860) (1a4b998f54b)