RSS

Makes it More Convenient for You to Proxy Dubbo Services in Apache APISIX

In this article, we introduced how to use Apache APISIX to implement a proxy for Dubbo Service. By introducing the dubbo-proxy plugin, you can build a simpler and more efficient traffic link for the back-end system of Dubbo framework.

Background

Apache Dubbo is a MicroService development framework open sourced by Alibaba and donated to Apache, which provides two key capabilities of RPC communication and microservice governance. It has not only been validated by Ali’s massive traffic in e-commerce scenario, but also been widely implemented in domestic technology companies.

In practical application scenarios, Apache Dubbo is generally used as the implementation framework for RPC calls between back-end systems, and when HTTP interfaces need to be provided to the front-end, the Dubbo Service is packaged as an HTTP interface through a “glue layer” and then delivered to the front-end system.

Apache APISIX is the top open source project of Apache Software Foundation and the most active open source gateway project today. As a dynamic, real-time, high-performance open source API gateway, Apache APISIX provides rich traffic management features such as load balancing, dynamic upstream, grayscale publishing, service meltdown, authentication, observability, and more.

Benefiting from the advantages of Apache Dubbo application scenarios, Apache APISIX is based on the open source project tengine/mod_dubbo module to equip Apache Dubbo services with HTTP gateway capabilities. Dubbo Service can be easily published as an HTTP service via the dubbo-proxy plugin.

Architecture Diagram

How to use

Getting Started: Installation and Use

Here we recommend using the Apache APISIX version 2.11 image for installation. This version of APISIX-Base has the Dubbo module compiled by default, so you can use the dubbo-proxy plugin directly.

In the next steps, we will use the dubbo-samples project for a partial demonstration. This project is a demo application implemented using Apache Dubbo, and in this article we use one of the sub-modules as the Dubbo Provider.

Before we get into the action, let’s take a brief look at the definition, configuration, and implementation of the Dubbo interface.

Interface implementation

public interface DemoService {

    /**
     * standard samples dubbo infterace demo
     * @param context pass http infos
     * @return Map<String, Object></> pass to response http
     **/
    Map<String, Object> apisixDubbo(Map<String, Object> httpRequestContext);
}

As shown above, the Dubbo interface is defined in a fixed way. The Map of method parameters represents the information passed by APISIX to the Dubbo Provider about the HTTP request (e.g. header, body, …). The Map of the method return value indicates how the Dubbo Provider passes some information to APISIX about the HTTP response to be returned.

After the interface information, the DemoService can be published via XML configuration.

<!-- service implementation, as same as regular local bean -->
<bean id="demoService" class="org.apache.dubbo.samples.provider.DemoServiceImpl"/>

<!-- declare the service interface to be exported -->
<dubbo:service interface="org.apache.dubbo.samples.apisix.DemoService" ref="demoService"/>

After the above configuration, the Consumer can access the apisixDubbo method through org.apache.dubbo.samples.apisix.DemoService The specific interface implementation is as follows.

public class DemoServiceImpl implements DemoService {
    @Override
    public Map<String, Object> apisixDubbo(Map<String, Object> httpRequestContext) {
        for (Map.Entry<String, Object> entry : httpRequestContext.entrySet()) {
            System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
        }

        Map<String, Object> ret = new HashMap<String, Object>();
        ret.put("body", "dubbo success\n"); // http response body
        ret.put("status", "200"); // http response status
        ret.put("test", "123"); // http response header

        return ret;
    }
}

:::note In the above code, DemoServiceImpl prints the received httpRequestContext and describes the HTTP response to the Dubbo request by returning a Map object with the specified Key. :::

Operation steps

  1. Start dubbo-samples.
  2. Enable the dubbo-proxy plugin in the config.yaml file.
# Add this in config.yaml
plugins:
  - ... # plugin you need
  - dubbo-proxy
  1. Create an Upstream that points to the Dubbo Provider.
curl http://127.0.0.1:9180/apisix/admin/upstreams/1  -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "nodes": {
        "127.0.0.1:20880": 1
    },
    "type": "roundrobin"
}'
  1. Expose an HTTP route for the DemoService.
curl http://127.0.0.1:9180/apisix/admin/routes/1  -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "host": "example.org"
    "uris": [
        "/demo"
    ],
    "plugins": {
        "dubbo-proxy": {
            "service_name": "org.apache.dubbo.samples.apisix.DemoService",
            "service_version": "0.0.0",
            "method": "apisixDubbo"
        }
    },
    "upstream_id": 1
}'
  1. Use the curl command to request Apache APISIX and view the returned results.
curl http://127.0.0.1:9080/demo  -H "Host: example.org"  -X POST --data '{"name": "hello"}'

< HTTP/1.1 200 OK
< Date: Sun, 26 Dec 2021 11:33:27 GMT
< Content-Type: text/plain; charset=utf-8
< Content-Length: 14
< Connection: keep-alive
< test: 123
< Server: APISIX/2.11.0
<
dubbo success

:::note The above code returns the test: 123 Header, and the dubbo success string as the body. This is the same as what we expected in the DemoServiceImpl code. :::

  1. You can view the logs of the Dubbo Provider.
Key = content-length, Value = 17
Key = host, Value = example.org
Key = content-type, Value = application/x-www-form-urlencoded
Key = body, Value = [B@70754265
Key = accept, Value = */*
Key = user-agent, Value = curl/7.80.0

:::note The Header and Body of the HTTP request are available through the httpRequestContext, where the Header is used as a Map element, while the Body has a fixed string “body” as the Key value and a Byte array as the Value. :::

Advanced: Complex Scenario Example

As you can see in the simple use case above, we do publish Dubbo Service as an HTTP service via Apache APISIX, but there are obvious limitations in its use. For example, the parameters and return values of the interface must be Map<String, Object>.

So, how do you expose the HTTP service through Apache APISIX if there is an interface in your project that is already defined, but does not meet the above restrictions?

Operation steps

For the above scenario, we can use the HTTP Request Body to describe the Service and Method to be invoked and the corresponding parameters, and then use the reflection mechanism of Java to realize the invocation of the target method. Finally, the return value is serialized to JSON and written to the HTTP Response Body.

This will further enhance the “HTTP to Dubbo” capability of Apache APISIX and apply it to all existing Dubbo services. For details, see the following.

  1. Add a Dubbo Service for existing projects to handle HTTP to Dubbo conversions in a unified way.
public class DubboInvocationParameter {
    private String type;
    private String value;
}

public class DubboInvocation {
    private String service;
    private String method;
    private DubboInvocationParameter[] parameters;
}

public interface HTTP2DubboService {
    Map<String, Object> invoke(Map<String, Object> context)  throws Exception;
}


@Component
public class HTTP2DubboServiceImpl implements HTTP2DubboService {

    @Autowired
    private ApplicationContext appContext;

    @Override
    public Map<String, Object> invoke(Map<String, Object> context) throws Exception {
        DubboInvocation invocation = JSONObject.parseObject((byte[]) context.get("body"), DubboInvocation.class);
        Object[] args = new Object[invocation.getParameters().size()];
        for (int i = 0; i < args.length; i++) {
            DubboInvocationParameter parameter = invocation.getParameters().get(i);
            args[i] = JSONObject.parseObject(parameter.getValue(), Class.forName(parameter.getType()));
        }

        Object svc = appContext.getBean(Class.forName(invocation.getService()));
        Object result = svc.getClass().getMethod(invocation.getMethod()).invoke(args);
        Map<String, Object> httpResponse = new HashMap<>();
        httpResponse.put("status", 200);
        httpResponse.put("body", JSONObject.toJSONString(result));
        return httpResponse;
    }

}
  1. Initiate the relevant call with the following command request.
curl http://127.0.0.1:9080/demo  -H "Host: example.org"  -X POST --data '
{
    "service": "org.apache.dubbo.samples.apisix.DemoService",
    "method": "createUser",
    "parameters": [
        {
            "type": "org.apache.dubbo.samples.apisix.User",
            "value": "{'name': 'hello'}"
        }
    ]
}'

Summary

In this article, we introduced how to use Apache APISIX to implement a proxy for Dubbo Service. By introducing the dubbo-proxy plugin, you can build a simpler and more efficient traffic link for the back-end system of Dubbo framework.

We hope that the above steps and use cases will provide you with ideas for using it in relevant scenarios. For more information about the dubbo-proxy plugin, please refer to the official documentation.