将dubbo暴露HTTP服务

通常来说一个dubbo服务都是对内的,也就是给内部调用的,但也有可能一个服务就是需要提供给外部使用,并且还不能有使用语言的局限性。

比较标准的做法是对外的服务我们统一提供一个openAPI,这样的调用方需要按照标准提供相应的appID以及密钥来进行验签才能使用。这样固然是比较规范和安全,但复杂度也不亚于开发一个单独的系统了。

这里所讲到的没有那么复杂,就只是把一个不需要各种权限检验的dubbo服务对外提供为HTTP服务。

实现思路:

1、定义HttpProviderConf配置类

主要用于保存声明需要对外提供服务的包名

package com.bsk.dubbo.http.conf;


import java.util.List;


public class HttpProviderConf {

    /**
     * 提供http访问的包
     */
    private List usePackage ;

    public List getUsePackage() {
        return usePackage;
    }

    public void setUsePackage(List usePackage) {
        this.usePackage = usePackage;
    }
}

2、封装请求响应类

package com.bsk.dubbo.http.req;
/**
 * 请求
 */
public class HttpRequest {
    private String param ;//入参
    private String service ;//请求service
    private String method ;//请求方法

    public String getParam() {
        return param;
    }

    public void setParam(String param) {
        this.param = param;
    }

    public String getService() {
        return service;
    }

    public void setService(String service) {
        this.service = service;
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }
}

其中param是用于存放真正调用dubbo服务时的入参,传入json在调用的时候解析成具体的参数对象。

service存放dubbo服务声明的interface API的包名。

method则是真正调用的方法名称。

package com.bsk.dubbo.http.rsp;

import java.io.Serializable;

/**
 * 响应
 */
public class HttpResponse implements Serializable{

    private static final long serialVersionUID = -552828440320737814L;

    private boolean success;//成功标志

    private String code;//信息码

    private String description;//描述

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

 3、编写暴露服务Controller接口

利用SpringMVC提供一个HTTP接口。
在该接口中通过入参进行反射找到具体的dubbo服务实现进行调用。

package com.bsk.dubbo.http.controller;

import com.alibaba.fastjson.JSON;
import com.bsk.dubbo.http.conf.HttpProviderConf;
import com.bsk.dubbo.http.req.HttpRequest;
import com.bsk.dubbo.http.rsp.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.*;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;


@Controller
@RequestMapping("/dubboAPI")
public class DubboController implements ApplicationContextAware {

    private final static Logger logger = LoggerFactory.getLogger(DubboController.class);

    @Autowired
    private HttpProviderConf httpProviderConf;

    /**
     * 缓存 Map
     */
    private final Map> cacheMap = new HashMap>();

    protected ApplicationContext applicationContext;


    @ResponseBody
    @RequestMapping(value = "/{service}/{method}", method = RequestMethod.POST)
    public String api(HttpRequest httpRequest, HttpServletRequest request,
                      @PathVariable String service,
                      @PathVariable String method) {
        logger.debug("ip:{}-httpRequest:{}", getIP(request), JSON.toJSONString(httpRequest));

        String invoke = invoke(httpRequest, service, method);
        logger.debug("callback :" + invoke);
        return invoke;

    }


    private String invoke(HttpRequest httpRequest, String service, String method) {
        httpRequest.setService(service);
        httpRequest.setMethod(method);

        HttpResponse response = new HttpResponse();

        logger.debug("input param:" + JSON.toJSONString(httpRequest));

        if (!CollectionUtils.isEmpty(httpProviderConf.getUsePackage())) {
            boolean isPac = false;
            for (String pac : httpProviderConf.getUsePackage()) {
                if (service.startsWith(pac)) {
                    isPac = true;
                    break;
                }
            }
            if (!isPac) {
                //调用的是未经配置的包
                logger.error("service is not correct,service=" + service);
                response.setCode("2");
                response.setSuccess(false);
                response.setDescription("service is not correct,service=" + service);
            }

        }
        try {
            Class serviceCla = cacheMap.get(service);
            if (serviceCla == null) {
                serviceCla = Class.forName(service);
                logger.debug("serviceCla:" + JSON.toJSONString(serviceCla));

                //设置缓存
                cacheMap.put(service, serviceCla);
            }
            Method[] methods = serviceCla.getMethods();
            Method targetMethod = null;
            for (Method m : methods) {
                if (m.getName().equals(method)) {
                    targetMethod = m;
                    break;
                }
            }

            if (method == null) {
                logger.error("method is not correct,method=" + method);
                response.setCode("2");
                response.setSuccess(false);
                response.setDescription("method is not correct,method=" + method);
            }

            Object bean = this.applicationContext.getBean(serviceCla);
            Object result = null;
            Class[] parameterTypes = targetMethod.getParameterTypes();
            if (parameterTypes.length == 0) {
                //没有参数
                result = targetMethod.invoke(bean);
            } else if (parameterTypes.length == 1) {
                Object json = JSON.parseObject(httpRequest.getParam(), parameterTypes[0]);
                result = targetMethod.invoke(bean, json);
            } else {
                logger.error("Can only have one parameter");
                response.setSuccess(false);
                response.setCode("2");
                response.setDescription("Can only have one parameter");
            }
            return JSON.toJSONString(result);

        } catch (ClassNotFoundException e) {
            logger.error("class not found", e);
            response.setSuccess(false);
            response.setCode("2");
            response.setDescription("class not found");
        } catch (InvocationTargetException e) {
            logger.error("InvocationTargetException", e);
            response.setSuccess(false);
            response.setCode("2");
            response.setDescription("InvocationTargetException");
        } catch (IllegalAccessException e) {
            logger.error("IllegalAccessException", e);
            response.setSuccess(false);
            response.setCode("2");
            response.setDescription("IllegalAccessException");
        }
        return JSON.toJSONString(response);
    }

    /**
     * 获取IP
     *
     * @param request
     * @return
     */
    private String getIP(HttpServletRequest request) {
        if (request == null) {
            return null;
        }
        String s = request.getHeader("X-Forwarded-For");
        if (s == null || s.length() == 0 || "unknown".equalsIgnoreCase(s)) {

            s = request.getHeader("Proxy-Client-IP");
        }
        if (s == null || s.length() == 0 || "unknown".equalsIgnoreCase(s)) {

            s = request.getHeader("WL-Proxy-Client-IP");
        }
        if (s == null || s.length() == 0 || "unknown".equalsIgnoreCase(s)) {
            s = request.getHeader("HTTP_CLIENT_IP");
        }
        if (s == null || s.length() == 0 || "unknown".equalsIgnoreCase(s)) {

            s = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (s == null || s.length() == 0 || "unknown".equalsIgnoreCase(s)) {

            s = request.getRemoteAddr();
        }
        if ("127.0.0.1".equals(s) || "0:0:0:0:0:0:0:1".equals(s)) {
            try {
                s = InetAddress.getLocalHost().getHostAddress();
            } catch (UnknownHostException unknownhostexception) {
                return "";
            }
        }
        return s;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

将这些实现封装成一个功能模块

具体使用请参考博文:https://crossoverjie.top/2017/05/07/SSM13/

 

4、测试示例

使用Jmeter工具进行测试: 

将dubbo暴露HTTP服务_第1张图片

 测试结果:将dubbo暴露HTTP服务_第2张图片

5、总结

通常来说这样提供的HTTP接口再实际中用的不多,但是很方便调试。

比如写了一个dubbo的查询接口,在测试环境或者是预发布环境中就可以直接通过HTTP请求的方式进行简单的测试,或者就是查询数据。比在Java中写单测来测试或查询快很多。

 

你可能感兴趣的:(Dubbo)