Rest 协议

本文将介绍 Dubbo 的 REST/HTTP 协议设计。

本文将介绍 Dubbo 的 REST/HTTP 协议设计。

RestProtocol 设计

原版本dubbo rest

consumer

restClient支持 依赖resteasy 不支持spring mvc

provider(较重)

依赖web container (tomcat,jetty,)servlet 模式,jaxrs netty server

新版本dubbo rest

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

1.注解解析

2.报文编解码

3.restClient

4.restServer(netty)

支持程度:

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

注解

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

Http 协议报文

  1. POST /test/path? HTTP/1.1
  2. Host: localhost:8080
  3. Connection: keep-alive
  4. Content-type: application/json
  5. {"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
  6. // 保证长连接
  7. Keep-Alive,Connection: keep-alive
  8. Keep-alive: 60
  9. // RPCContext Attachment
  10. userId: 123456

支持粒度

数据位置content-typespring注解resteasy注解
body无要求ReuqestBody 无注解即为body
querystring(?test=demo)无要求RequestParamQueryParam
header无要求RequestHeaderPathParam
formapplication/x-www-form-urlencodedRequestParam ReuqestBodyFormParam
path无要求PathVariablePathParam
method无要求PostMapping GetMappingGET POST
urlPostMapping GetMapping path属性Path
content-typePostMapping GetMapping consumers属性Consumers
AcceptPostMapping GetMapping produces属性Produces

rest注解解析

ServiceRestMetadataResolver

  1. JAXRSServiceRestMetadataResolver
  2. SpringMvcServiceRestMetadataResolver

ServiceRestMetadata

  1. public class ServiceRestMetadata implements Serializable {
  2. private String serviceInterface; // com.demo.TestInterface
  3. private String version;// 1.0.0
  4. private String group;// demo
  5. private Set<RestMethodMetadata> meta;// method 元信息
  6. private int port;// 端口 for provider service key
  7. private boolean consumer;// consumer 标志
  8. /**
  9. * make a distinction between mvc & resteasy
  10. */
  11. private Class codeStyle;//
  12. /**
  13. * for provider
  14. */
  15. private Map<PathMatcher, RestMethodMetadata> pathToServiceMap;
  16. /**
  17. * for consumer
  18. */
  19. private Map<String, Map<ParameterTypesComparator, RestMethodMetadata>> methodToServiceMa

RestMethodMetadata

  1. public class RestMethodMetadata implements Serializable {
  2. private MethodDefinition method; // method 定义信息(name ,pramType,returnType)
  3. private RequestMetadata request;// 请求元信息
  4. private Integer urlIndex;
  5. private Integer bodyIndex;
  6. private Integer headerMapIndex;
  7. private String bodyType;
  8. private Map<Integer, Collection<String>> indexToName;
  9. private List<String> formParams;
  10. private Map<Integer, Boolean> indexToEncoded;
  11. private ServiceRestMetadata serviceRestMetadata;
  12. private List<ArgInfo> argInfos;
  13. private Method reflectMethod;
  14. /**
  15. * make a distinction between mvc & resteasy
  16. */
  17. 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;
  10. /**
  11. * param annotation type
  12. */
  13. private Class paramAnnotationType;
  14. /**
  15. * param Type
  16. */
  17. private Class paramType;
  18. /**
  19. * param name
  20. */
  21. private String paramName;
  22. /**
  23. * url split("/") String[n] index
  24. */
  25. private int urlSplitIndex;
  26. private Object defaultValue;
  27. private boolean formContentType;

RequestMeatadata

  1. public class RequestMetadata implements Serializable {
  2. private static final long serialVersionUID = -240099840085329958L;
  3. private String method;// 请求method
  4. private String path;// 请求url
  5. private Map<String, List<String>> params // param参数?拼接
  6. private Map<String, List<String>> headers// header;
  7. private Set<String> consumes // content-type;
  8. private Set<String> produces // Accept;

Consumer 代码

refer

  1. @Override
  2. protected <T> Invoker<T> protocolBindingRefer(final Class<T> type, final URL url) throws RpcException {
  3. // restClient spi创建
  4. ReferenceCountedClient<? extends RestClient> refClient =
  5. clients.computeIfAbsent(url.getAddress(), key -> createReferenceCountedClient(url, clients));
  6. refClient.retain();
  7. // resolve metadata
  8. Map<String, Map<ParameterTypesComparator, RestMethodMetadata>> metadataMap = MetadataResolver.resolveConsumerServiceMetadata(type, url);
  9. ReferenceCountedClient<? extends RestClient> finalRefClient = refClient;
  10. Invoker<T> invoker = new AbstractInvoker<T>(type, url, new String[]{INTERFACE_KEY, GROUP_KEY, TOKEN_KEY}) {
  11. @Override
  12. protected Result doInvoke(Invocation invocation) {
  13. try {
  14. // 获取 method的元信息
  15. RestMethodMetadata restMethodMetadata = metadataMap.get(invocation.getMethodName()).get(ParameterTypesComparator.getInstance(invocation.getParameterTypes()));
  16. RequestTemplate requestTemplate = new RequestTemplate(invocation, restMethodMetadata.getRequest().getMethod(), url.getAddress(), getContextPath(url));
  17. HttpConnectionCreateContext httpConnectionCreateContext = new HttpConnectionCreateContext();
  18. // TODO dynamic load config
  19. httpConnectionCreateContext.setConnectionConfig(new HttpConnectionConfig());
  20. httpConnectionCreateContext.setRequestTemplate(requestTemplate);
  21. httpConnectionCreateContext.setRestMethodMetadata(restMethodMetadata);
  22. httpConnectionCreateContext.setInvocation(invocation);
  23. httpConnectionCreateContext.setUrl(url);
  24. // http 信息构建拦截器
  25. for (HttpConnectionPreBuildIntercept intercept : httpConnectionPreBuildIntercepts) {
  26. intercept.intercept(httpConnectionCreateContext);
  27. }
  28. CompletableFuture<RestResult> future = finalRefClient.getClient().send(requestTemplate);
  29. CompletableFuture<AppResponse> responseFuture = new CompletableFuture<>();
  30. AsyncRpcResult asyncRpcResult = new AsyncRpcResult(responseFuture, invocation);
  31. // response 处理
  32. future.whenComplete((r, t) -> {
  33. if (t != null) {
  34. responseFuture.completeExceptionally(t);
  35. } else {
  36. AppResponse appResponse = new AppResponse();
  37. try {
  38. int responseCode = r.getResponseCode();
  39. MediaType mediaType = MediaType.TEXT_PLAIN;
  40. if (400 < responseCode && responseCode < 500) {
  41. throw new HttpClientException(r.getMessage());
  42. } else if (responseCode >= 500) {
  43. throw new RemoteServerInternalException(r.getMessage());
  44. } else if (responseCode < 400) {
  45. mediaType = MediaTypeUtil.convertMediaType(r.getContentType());
  46. }
  47. Object value = HttpMessageCodecManager.httpMessageDecode(r.getBody(),
  48. restMethodMetadata.getReflectMethod().getReturnType(), mediaType);
  49. appResponse.setValue(value);
  50. Map<String, String> headers = r.headers()
  51. .entrySet()
  52. .stream()
  53. .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
  54. appResponse.setAttachments(headers);
  55. responseFuture.complete(appResponse);
  56. } catch (Exception e) {
  57. responseFuture.completeExceptionally(e);
  58. }
  59. }
  60. });
  61. return asyncRpcResult;
  62. } catch (RpcException e) {
  63. if (e.getCode() == RpcException.UNKNOWN_EXCEPTION) {
  64. e.setCode(getErrorCode(e.getCause()));
  65. }
  66. throw e;
  67. }
  68. }
  69. @Override
  70. public void destroy() {
  71. super.destroy();
  72. invokers.remove(this);
  73. destroyInternal(url);
  74. }
  75. };
  76. invokers.add(invoker);
  77. 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. }
  11. // TODO addAll metadataMap to RPCInvocationBuilder metadataMap
  12. Map<PathMatcher, RestMethodMetadata> metadataMap = MetadataResolver.resolveProviderServiceMetadata(url.getServiceModel().getProxyObject().getClass(),url);
  13. PathAndInvokerMapper.addPathAndInvoker(metadataMap, invoker);
  14. final Runnable runnable = doExport(proxyFactory.getProxy(invoker, true), invoker.getInterface(), invoker.getUrl());
  15. exporter = new AbstractExporter<T>(invoker) {
  16. @Override
  17. public void afterUnExport() {
  18. exporterMap.remove(uri);
  19. if (runnable != null) {
  20. try {
  21. runnable.run();
  22. } catch (Throwable t) {
  23. logger.warn(PROTOCOL_UNSUPPORTED, "", "", t.getMessage(), t);
  24. }
  25. }
  26. }
  27. };
  28. exporterMap.put(uri, exporter);
  29. return exporter;
  30. }

RestHandler

  1. private class RestHandler implements HttpHandler<HttpServletRequest, HttpServletResponse> {
  2. @Override
  3. public void handle(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException {
  4. // 有servlet reuqest 和nettyRequest
  5. RequestFacade request = RequestFacadeFactory.createRequestFacade(servletRequest);
  6. RpcContext.getServiceContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort());
  7. // dispatcher.service(request, servletResponse);
  8. Pair<RpcInvocation, Invoker> build = null;
  9. try {
  10. // 根据请求信息创建 RPCInvocation
  11. build = RPCInvocationBuilder.build(request, servletRequest, servletResponse);
  12. } catch (PathNoFoundException e) {
  13. servletResponse.setStatus(404);
  14. }
  15. Invoker invoker = build.getSecond();
  16. Result invoke = invoker.invoke(build.getFirst());
  17. // TODO handling exceptions
  18. if (invoke.hasException()) {
  19. servletResponse.setStatus(500);
  20. } else {
  21. try {
  22. Object value = invoke.getValue();
  23. String accept = request.getHeader(RestConstant.ACCEPT);
  24. MediaType mediaType = MediaTypeUtil.convertMediaType(accept);
  25. // TODO write response
  26. HttpMessageCodecManager.httpMessageEncode(servletResponse.getOutputStream(), value, invoker.getUrl(), mediaType);
  27. servletResponse.setStatus(200);
  28. } catch (Exception e) {
  29. servletResponse.setStatus(500);
  30. }
  31. }
  32. // TODO add Attachment header
  33. }
  34. }

RPCInvocationBuilder

  1. {
  2. private static final ParamParserManager paramParser = new ParamParserManager();
  3. public static Pair<RpcInvocation, Invoker> build(RequestFacade request, Object servletRequest, Object servletResponse) {
  4. // 获取invoker
  5. Pair<Invoker, RestMethodMetadata> invokerRestMethodMetadataPair = getRestMethodMetadata(request);
  6. RpcInvocation rpcInvocation = createBaseRpcInvocation(request, invokerRestMethodMetadataPair.getSecond());
  7. ProviderParseContext parseContext = createParseContext(request, servletRequest, servletResponse, invokerRestMethodMetadataPair.getSecond());
  8. // 参数构建
  9. Object[] args = paramParser.providerParamParse(parseContext);
  10. rpcInvocation.setArguments(args);
  11. return Pair.make(rpcInvocation, invokerRestMethodMetadataPair.getFirst());
  12. }
  13. private static ProviderParseContext createParseContext(RequestFacade request, Object servletRequest, Object servletResponse, RestMethodMetadata restMethodMetadata) {
  14. ProviderParseContext parseContext = new ProviderParseContext(request);
  15. parseContext.setResponse(servletResponse);
  16. parseContext.setRequest(servletRequest);
  17. Object[] objects = new Object[restMethodMetadata.getArgInfos().size()];
  18. parseContext.setArgs(Arrays.asList(objects));
  19. parseContext.setArgInfos(restMethodMetadata.getArgInfos());
  20. return parseContext;
  21. }
  22. private static RpcInvocation createBaseRpcInvocation(RequestFacade request, RestMethodMetadata restMethodMetadata) {
  23. RpcInvocation rpcInvocation = new RpcInvocation();
  24. int localPort = request.getLocalPort();
  25. String localAddr = request.getLocalAddr();
  26. int remotePort = request.getRemotePort();
  27. String remoteAddr = request.getRemoteAddr();
  28. String HOST = request.getHeader(RestConstant.HOST);
  29. String GROUP = request.getHeader(RestConstant.GROUP);
  30. String PATH = request.getHeader(RestConstant.PATH);
  31. String VERSION = request.getHeader(RestConstant.VERSION);
  32. String METHOD = restMethodMetadata.getMethod().getName();
  33. String[] PARAMETER_TYPES_DESC = restMethodMetadata.getMethod().getParameterTypes();
  34. rpcInvocation.setParameterTypes(restMethodMetadata.getReflectMethod().getParameterTypes());
  35. rpcInvocation.setMethodName(METHOD);
  36. rpcInvocation.setAttachment(RestConstant.GROUP, GROUP);
  37. rpcInvocation.setAttachment(RestConstant.METHOD, METHOD);
  38. rpcInvocation.setAttachment(RestConstant.PARAMETER_TYPES_DESC, PARAMETER_TYPES_DESC);
  39. rpcInvocation.setAttachment(RestConstant.PATH, PATH);
  40. rpcInvocation.setAttachment(RestConstant.VERSION, VERSION);
  41. rpcInvocation.setAttachment(RestConstant.HOST, HOST);
  42. rpcInvocation.setAttachment(RestConstant.REMOTE_ADDR, remoteAddr);
  43. rpcInvocation.setAttachment(RestConstant.LOCAL_ADDR, localAddr);
  44. rpcInvocation.setAttachment(RestConstant.REMOTE_PORT, remotePort);
  45. rpcInvocation.setAttachment(RestConstant.LOCAL_PORT, localPort);
  46. Enumeration<String> attachments = request.getHeaders(RestConstant.DUBBO_ATTACHMENT_HEADER);
  47. while (attachments != null && attachments.hasMoreElements()) {
  48. String s = attachments.nextElement();
  49. String[] split = s.split("=");
  50. rpcInvocation.setAttachment(split[0], split[1]);
  51. }
  52. // TODO set path,version,group and so on
  53. return rpcInvocation;
  54. }
  55. private static Pair<Invoker, RestMethodMetadata> getRestMethodMetadata(RequestFacade request) {
  56. String path = request.getRequestURI();
  57. String version = request.getHeader(RestConstant.VERSION);
  58. String group = request.getHeader(RestConstant.GROUP);
  59. int port = request.getIntHeader(RestConstant.REST_PORT);
  60. return PathAndInvokerMapper.getRestMethodMetadata(path, version, group, port);
  61. }
  62. }

编码示例

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);
  6. @RequestMapping(value = "/error", method = RequestMethod.GET)
  7. String error();
  8. @RequestMapping(value = "/say", method = RequestMethod.POST, consumes = MediaType.TEXT_PLAIN_VALUE)
  9. String sayHello(@RequestBody String name);
  10. }

resteasy:

  1. @Path("/demoService")
  2. public interface RestDemoService {
  3. @GET
  4. @Path("/hello")
  5. Integer hello(@QueryParam("a")Integer a,@QueryParam("b") Integer b);
  6. @GET
  7. @Path("/error")
  8. String error();
  9. @POST
  10. @Path("/say")
  11. @Consumes({MediaType.TEXT_PLAIN})
  12. String sayHello(String name);
  13. boolean isCalled();
  14. }

impl(service)

  1. @DubboService()
  2. public class RestDemoServiceImpl implements RestDemoService {
  3. private static Map<String, Object> context;
  4. private boolean called;
  5. @Override
  6. public String sayHello(String name) {
  7. called = true;
  8. return "Hello, " + name;
  9. }
  10. public boolean isCalled() {
  11. return called;
  12. }
  13. @Override
  14. public Integer hello(Integer a, Integer b) {
  15. context = RpcContext.getServerAttachment().getObjectAttachments();
  16. return a + b;
  17. }
  18. @Override
  19. public String error() {
  20. throw new RuntimeException();
  21. }
  22. public static Map<String, Object> getAttachments() {
  23. return context;
  24. }
  25. }

流程图

Consumer

image

Provider(RestServer)

image

场景

1.体系互通

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

互通条件:

协议DubboSpringCloud Alibaba互通
通信协议restspring 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

最后修改 April 15, 2023: Update protocol-http.md (#2535) (c2f579b2413)