A lighter, Dubbo-style Rest protocol for interoperation within microservices (Spring Cloud Alibaba).
Annotation Parsing
Message Encoding and Decoding
RestClient
RestServer (Netty)
Supported content-types: text, json, xml, form (to be extended later).
Annotations:
param, header, body, pathvariable (spring mvc & resteasy)
POST /test/path?  HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-type: application/json
{"name":"dubbo","age":10,"address":"hangzhou"}
// service key header
path: com.demo.TestInterface
group: demo
port: 80
version: 1.0.0
// Ensure long connection
Keep-Alive, Connection: keep-alive
Keep-alive: 60
// RPCContext Attachment
userId: 123456
| Data Location | content-type | spring Annotation | resteasy Annotation | 
|---|---|---|---|
| body | No requirements | RequestBody | No annotation means body | 
| querystring(?test=demo) | No requirements | RequestParam | QueryParam | 
| header | No requirements | RequestHeader | PathParam | 
| form | application/x-www-form-urlencoded | RequestParam RequestBody | FormParam | 
| path | No requirements | PathVariable | PathParam | 
| method | No requirements | PostMapping GetMapping | GET POST | 
| url | PostMapping GetMapping path attribute | Path | |
| content-type | PostMapping GetMapping consumers attribute | Consumers | |
| Accept | PostMapping GetMapping produces attribute | Produces | 
JAXRSServiceRestMetadataResolver
SpringMvcServiceRestMetadataResolver
ServiceRestMetadata
public class ServiceRestMetadata implements Serializable {
    private String serviceInterface; // com.demo.TestInterface
    private String version; // 1.0.0
    private String group; // demo
    private Set<RestMethodMetadata> meta; // method metadata
    private int port; // port for provider service key
    private boolean consumer; // consumer flag
    /**
     * makes a distinction between mvc & resteasy
     */
    private Class codeStyle; // 
     /**
     * for provider
     */
    private Map<PathMatcher, RestMethodMetadata> pathToServiceMap;
    
    /**
    * for consumer
    */
    private Map<String, Map<ParameterTypesComparator, RestMethodMetadata>> methodToServiceMa
RestMethodMetadata
public class RestMethodMetadata implements Serializable {
    private MethodDefinition method; // method definition info (name ,paramType, returnType)
    private RequestMetadata request; // request metadata
    private Integer urlIndex;
    private Integer bodyIndex;
    private Integer headerMapIndex;
    private String bodyType;
    private Map<Integer, Collection<String>> indexToName;
    private List<String> formParams;
    private Map<Integer, Boolean> indexToEncoded;
    private ServiceRestMetadata serviceRestMetadata;
    private List<ArgInfo> argInfos;
    private Method reflectMethod;
    /**
     * makes a distinction between mvc & resteasy
     */
    private Class codeStyle;
ArgInfo
public class ArgInfo {
    /**
     * method arg index 0,1,2,3
     */
    private int index;
    /**
     * method annotation name or name
     */
    private String annotationNameAttribute;
    /**
     * param annotation type
     */
    private Class paramAnnotationType;
    /**
     * param Type
     */
    private Class paramType;
    /**
     * param name
     */
    private String paramName;
    /**
     * url split("/") String[n] index
     */
    private int urlSplitIndex;
    private Object defaultValue;
    private boolean formContentType;
RequestMetadata
public class RequestMetadata implements Serializable {
    private static final long serialVersionUID = -240099840085329958L;
    private String method; // request method
    private String path; // request url
    private Map<String, List<String>> params; // param parameters? splicing
    private Map<String, List<String>> headers; // header;
    private Set<String> consumes; // content-type;
    private Set<String> produces; // Accept;
refer:
 @Override
    protected <T> Invoker<T> protocolBindingRefer(final Class<T> type, final URL url) throws RpcException {
        // restClient spi creation
        ReferenceCountedClient<? extends RestClient> refClient =
            clients.computeIfAbsent(url.getAddress(), key -> createReferenceCountedClient(url, clients));
        refClient.retain();
        // resolve metadata
        Map<String, Map<ParameterTypesComparator, RestMethodMetadata>> metadataMap = MetadataResolver.resolveConsumerServiceMetadata(type, url);
        ReferenceCountedClient<? extends RestClient> finalRefClient = refClient;
        Invoker<T> invoker = new AbstractInvoker<T>(type, url, new String[]{INTERFACE_KEY, GROUP_KEY, TOKEN_KEY}) {
            @Override
            protected Result doInvoke(Invocation invocation) {
                try {
                    // Obtain method metadata
                    RestMethodMetadata restMethodMetadata = metadataMap.get(invocation.getMethodName()).get(ParameterTypesComparator.getInstance(invocation.getParameterTypes()));
                    RequestTemplate requestTemplate = new RequestTemplate(invocation, restMethodMetadata.getRequest().getMethod(), url.getAddress(), getContextPath(url));
                    HttpConnectionCreateContext httpConnectionCreateContext = new HttpConnectionCreateContext();
                    // TODO dynamic load config
                    httpConnectionCreateContext.setConnectionConfig(new HttpConnectionConfig());
                    httpConnectionCreateContext.setRequestTemplate(requestTemplate);
                    httpConnectionCreateContext.setRestMethodMetadata(restMethodMetadata);
                    httpConnectionCreateContext.setInvocation(invocation);
                    httpConnectionCreateContext.setUrl(url);
                                    // http information building interceptor
                    for (HttpConnectionPreBuildIntercept intercept : httpConnectionPreBuildIntercepts) {
                        intercept.intercept(httpConnectionCreateContext);
                    }
                    CompletableFuture<RestResult> future = finalRefClient.getClient().send(requestTemplate);
                    CompletableFuture<AppResponse> responseFuture = new CompletableFuture<>();
                    AsyncRpcResult asyncRpcResult = new AsyncRpcResult(responseFuture, invocation);
                    // response handling
                    future.whenComplete((r, t) -> {
                        if (t != null) {
                            responseFuture.completeExceptionally(t);
                        } else {
                            AppResponse appResponse = new AppResponse();
                            try {
                                int responseCode = r.getResponseCode();
                                MediaType mediaType = MediaType.TEXT_PLAIN;
                                if (400 < responseCode && responseCode < 500) {
                                    throw new HttpClientException(r.getMessage());
                                } else if (responseCode >= 500) {
                                    throw new RemoteServerInternalException(r.getMessage());
                                } else if (responseCode < 400) {
                                    mediaType = MediaTypeUtil.convertMediaType(r.getContentType());
                                }
                                Object value = HttpMessageCodecManager.httpMessageDecode(r.getBody(),
                                    restMethodMetadata.getReflectMethod().getReturnType(), mediaType);
                                appResponse.setValue(value);
                                Map<String, String> headers = r.headers()
                                    .entrySet()
                                    .stream()
                                    .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
                                appResponse.setAttachments(headers);
                                responseFuture.complete(appResponse);
                            } catch (Exception e) {
                                responseFuture.completeExceptionally(e);
                            }
                        }
                    });
                    return asyncRpcResult;
                } catch (RpcException e) {
                    if (e.getCode() == RpcException.UNKNOWN_EXCEPTION) {
                        e.setCode(getErrorCode(e.getCause()));
                    }
                    throw e;
                }
            }
            @Override
            public void destroy() {
                super.destroy();
                invokers.remove(this);
                destroyInternal(url);
            }
        };
        invokers.add(invoker);
        return invoker;
export:
 public <T> Exporter<T> export(final Invoker<T> invoker) throws RpcException {
        URL url = invoker.getUrl();
        final String uri = serviceKey(url);
        Exporter<T> exporter = (Exporter<T>) exporterMap.get(uri);
        if (exporter != null) {
            // When modifying the configuration through override, you need to re-expose the newly modified service.
            if (Objects.equals(exporter.getInvoker().getUrl(), invoker.getUrl())) {
                return exporter;
            }
        }
        // TODO addAll metadataMap to RPCInvocationBuilder metadataMap
        Map<PathMatcher, RestMethodMetadata> metadataMap = MetadataResolver.resolveProviderServiceMetadata(url.getServiceModel().getProxyObject().getClass(),url);
        PathAndInvokerMapper.addPathAndInvoker(metadataMap, invoker);
        final Runnable runnable = doExport(proxyFactory.getProxy(invoker, true), invoker.getInterface(), invoker.getUrl());
        exporter = new AbstractExporter<T>(invoker) {
            @Override
            public void afterUnExport() {
                exporterMap.remove(uri);
                if (runnable != null) {
                    try {
                        runnable.run();
                    } catch (Throwable t) {
                        logger.warn(PROTOCOL_UNSUPPORTED, "", "", t.getMessage(), t);
                    }
                }
            }
        };
        exporterMap.put(uri, exporter);
        return exporter;
    }
RestHandler
 private class RestHandler implements HttpHandler<HttpServletRequest, HttpServletResponse> {
        @Override
        public void handle(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException {
             // There are servlet request and nettyRequest
            RequestFacade request = RequestFacadeFactory.createRequestFacade(servletRequest);
            RpcContext.getServiceContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort());
//            dispatcher.service(request, servletResponse);
            Pair<RpcInvocation, Invoker> build = null;
            try {
                // Create RPCInvocation based on request information
                build = RPCInvocationBuilder.build(request, servletRequest, servletResponse);
            } catch (PathNoFoundException e) {
                servletResponse.setStatus(404);
            }
            Invoker invoker = build.getSecond();
            Result invoke = invoker.invoke(build.getFirst());
            // TODO handling exceptions
            if (invoke.hasException()) {
                servletResponse.setStatus(500);
            } else {
                try {
                    Object value = invoke.getValue();
                    String accept = request.getHeader(RestConstant.ACCEPT);
                    MediaType mediaType = MediaTypeUtil.convertMediaType(accept);
                    // TODO write response
                    HttpMessageCodecManager.httpMessageEncode(servletResponse.getOutputStream(), value, invoker.getUrl(), mediaType);
                    servletResponse.setStatus(200);
                } catch (Exception e) {
                    servletResponse.setStatus(500);
                }
            }
            // TODO add Attachment header
        }
    }
RPCInvocationBuilder
{
    private static final ParamParserManager paramParser = new ParamParserManager();
    public static Pair<RpcInvocation, Invoker> build(RequestFacade request, Object servletRequest, Object servletResponse) {
 
        // Obtain invoker
        Pair<Invoker, RestMethodMetadata> invokerRestMethodMetadataPair = getRestMethodMetadata(request);
        RpcInvocation rpcInvocation = createBaseRpcInvocation(request, invokerRestMethodMetadataPair.getSecond());
        ProviderParseContext parseContext = createParseContext(request, servletRequest, servletResponse, invokerRestMethodMetadataPair.getSecond());
        // Parameter building
        Object[] args = paramParser.providerParamParse(parseContext);
        rpcInvocation.setArguments(args);
        return Pair.make(rpcInvocation, invokerRestMethodMetadataPair.getFirst());
    }
    private static ProviderParseContext createParseContext(RequestFacade request, Object servletRequest, Object servletResponse, RestMethodMetadata restMethodMetadata) {
        ProviderParseContext parseContext = new ProviderParseContext(request);
        parseContext.setResponse(servletResponse);
        parseContext.setRequest(servletRequest);
        Object[] objects = new Object[restMethodMetadata.getArgInfos().size()];
        parseContext.setArgs(Arrays.asList(objects));
        parseContext.setArgInfos(restMethodMetadata.getArgInfos());
        return parseContext;
    }
    private static RpcInvocation createBaseRpcInvocation(RequestFacade request, RestMethodMetadata restMethodMetadata) {
        RpcInvocation rpcInvocation = new RpcInvocation();
        int localPort = request.getLocalPort();
        String localAddr = request.getLocalAddr();
        int remotePort = request.getRemotePort();
        String remoteAddr = request.getRemoteAddr();
        String HOST = request.getHeader(RestConstant.HOST);
        String GROUP = request.getHeader(RestConstant.GROUP);
        String PATH = request.getHeader(RestConstant.PATH);
        String VERSION = request.getHeader(RestConstant.VERSION);
        String METHOD = restMethodMetadata.getMethod().getName();
        String[] PARAMETER_TYPES_DESC = restMethodMetadata.getMethod().getParameterTypes();
        rpcInvocation.setParameterTypes(restMethodMetadata.getReflectMethod().getParameterTypes());
        rpcInvocation.setMethodName(METHOD);
        rpcInvocation.setAttachment(RestConstant.GROUP, GROUP);
        rpcInvocation.setAttachment(RestConstant.METHOD, METHOD);
        rpcInvocation.setAttachment(RestConstant.PARAMETER_TYPES_DESC, PARAMETER_TYPES_DESC);
        rpcInvocation.setAttachment(RestConstant.PATH, PATH);
        rpcInvocation.setAttachment(RestConstant.VERSION, VERSION);
        rpcInvocation.setAttachment(RestConstant.HOST, HOST);
        rpcInvocation.setAttachment(RestConstant.REMOTE_ADDR, remoteAddr);
        rpcInvocation.setAttachment(RestConstant.LOCAL_ADDR, localAddr);
        rpcInvocation.setAttachment(RestConstant.REMOTE_PORT, remotePort);
        rpcInvocation.setAttachment(RestConstant.LOCAL_PORT, localPort);
        Enumeration<String> attachments = request.getHeaders(RestConstant.DUBBO_ATTACHMENT_HEADER);
        while (attachments != null && attachments.hasMoreElements()) {
            String s =  attachments.nextElement();
            String[] split = s.split("=");
            rpcInvocation.setAttachment(split[0], split[1]);
        }
        // TODO set path, version, group and so on
        return rpcInvocation;
    }
    private static Pair<Invoker, RestMethodMetadata> getRestMethodMetadata(RequestFacade request) {
        String path = request.getRequestURI();
        String version = request.getHeader(RestConstant.VERSION);
        String group = request.getHeader(RestConstant.GROUP);
        int port = request.getIntHeader(RestConstant.REST_PORT);
        return PathAndInvokerMapper.getRestMethodMetadata(path, version, group, port);
    }
}
API
mvc:
@RestController()
@RequestMapping("/demoService")
public interface DemoService {
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    Integer hello(@RequestParam Integer a, @RequestParam Integer b);
    @RequestMapping(value = "/error", method = RequestMethod.GET)
    String error();
    @RequestMapping(value = "/say", method = RequestMethod.POST, consumes = MediaType.TEXT_PLAIN_VALUE)
    String sayHello(@RequestBody String name);
}
resteasy:
@Path("/demoService")
public interface RestDemoService {
    @GET
    @Path("/hello")
    Integer hello(@QueryParam("a")Integer a, @QueryParam("b") Integer b);
    @GET
    @Path("/error")
    String error();
    @POST
    @Path("/say")
    @Consumes({MediaType.TEXT_PLAIN})
    String sayHello(String name);
    boolean isCalled();
}
impl(service)
@DubboService()
public class RestDemoServiceImpl implements RestDemoService {
    private static Map<String, Object> context;
    private boolean called;
    @Override
    public String sayHello(String name) {
        called = true;
        return "Hello, " + name;
    }
    public boolean isCalled() {
        return called;
    }
    @Override
    public Integer hello(Integer a, Integer b) {
        context = RpcContext.getServerAttachment().getObjectAttachments();
        return a + b;
    }
    @Override
    public String error() {
        throw new RuntimeException();
    }
    public static Map<String, Object> getAttachments() {
        return context;
    }
}
Consumer

Provider (RestServer)

Non-Dubbo intercommunication (Spring Cloud Alibaba intercommunication)
Intercommunication conditions:
| Protocol | Dubbo | Spring Cloud Alibaba | Intercommunication | |
|---|---|---|---|---|
| Communication Protocol | rest | spring web/resteasy encoding style | integrates feignclient, ribbon (spring web encoding style) | Yes | 
| triple | ||||
| dubbo | ||||
| grpc | ||||
| hessian | ||||
| Registration Center | zookeeper | |||
| nacos | Supports | Supports | Application-level registration | 
Complete application-level registration, (dubo2-dubbo3 migration), dubbo version upgrade


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



Rest encoding style
HTTP protocol is more universally applicable for cross-language calls
Dubbo rest calls other HTTP services
Other HTTP clients call dubbo rest
dubbo restServer can directly interact with other web services, browsers, etc.
org/apache/dubbo/rpc/protocol/rest/RestProtocol.java:157 dynamic load config
org/apache/dubbo/remoting/http/factory/AbstractHttpClientFactory.java:50 load config HttpClientConfig
org/apache/dubbo/rpc/protocol/rest/annotation/metadata/MetadataResolver.java:52 support Dubbo style service
org/apache/dubbo/remoting/http/restclient/HttpClientRestClient.java:120 TODO config
org/apache/dubbo/remoting/http/restclient/HttpClientRestClient.java:140 TODO close judge
org/apache/dubbo/rpc/protocol/rest/message/decode/MultiValueCodec.java:35 TODO java bean get set convert
Implement HTTP protocol-supporting NettyServer based on Netty
No annotation protocol definition
Supplement scenarios on the official website