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

Provider(RestServer)

场景 :
非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版本升级


3.多协议发布
配置:
1. <dubbo:service interface="org.apache.dubbo.samples.DemoService" protocol="dubbo, grpc,rest"/>
4.跨语言

5.多协议交互

6.协议迁移

rest编码风格
Http协议更通用跨语言调用
dubbo rest 对其他http服务 进行调用
其他httpclient 对dubbo rest进行调用
dubbo restServer 可以与其他web服务,浏览器等客户端直接进行http交互
consumer TODOLIST(功能已经初步实现,可以调通解析response)
- 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)
