问题

最近在学习SpringCloud , 以及将要在公司内部部署和推广的过程中,发现网关既需要支持 http ,同时也需要支持 dubbo,并且网关只需要支持http即可,那么在网关的内部就需要将http协议转换成dubbo协议,在内部做又有2个处理方式

  • 1、在网关层面处理
    • 优点
      • 直接利用dubbo的泛化功能
      • 服务提供者不需要进行额外的处理
    • 缺点
      • 在网关层需要进行dubbo的tcp连接,如果业务的网络环境比较特殊,那么这一套是较难维护的
      • 接口的交互较为复杂,泛化需要将参数类型,参数等等进行传递,而这些服务提供者本身其实是存在的。
  • 2、在dubbo#provider层面进行处理
    • 优点
      • 直接对接http协议,不必处理额外的网络环境
      • 仅需要传递dubbo服务需要的参数,不必传递额外的参数类型
    • 缺点
      • 如何让服务提供者支持http转dubbo.

通过上述的比较,以及公司业务上处理,我们选择了第二种进行处理.

开发

一开始我们的服务是通过tomcat或者内置容器的SpringBoot进行暴露的,如果访问dubbo的话,过程就是 http --> nginx ---> tomcat ---> springmvc ---> dubbo 这个过程,而现在我们需要做的就是将这个过程中的SpringMVC这一块进行移除,变成 http --> nginx ---> tomcat ---> dubbo,从而直接支持http被dubbo处理。于是我通过SpringMVC的处理机制将Controller这一块移除掉,达到了我们的目的,接下来看如何一步一步实现的.

假设url = /dubbo/*

  • 通过包装Servlet统一处理对接的http。
  1. <servlet>
  2. <servlet-name>GatewayServlet</servlet-name>
  3. <servlet-class>com.xxx.gateway.dubbo.web.GatewayServlet</servlet-class>
  4. </servlet>
  5. <servlet-mapping>
  6. <servlet-name>GatewayServlet</servlet-name>
  7. <url-pattern>/dubbo/*</url-pattern>
  8. </servlet-mapping>
  • 通过http参数获取dubbo服务的接口,版本等等
  1. String inf = servletRequest.getParameter("serviceName");
  2. Assert.notNull(inf, "接口不能为空!");
  3. String parameter = servletRequest.getParameter("method");
  4. Assert.notNull(inf, "方法不能为空!");
  5. String uGroup = servletRequest.getParameter("group");
  6. String vVersion = servletRequest.getParameter("version");
  7. Assert.notNull(vVersion, "版本不能为空!");
  • 获取dubbo服务
  1. String[] beanNamesForType = this.applicationContext.getBeanNamesForType(ServiceConfig.class);
  2. Object ref = null;
  3. for (String service : beanNamesForType) {
  4. ServiceConfig serviceConfig = (ServiceConfig) this.applicationContext.getBean(service);
  5. String version = serviceConfig.getVersion();
  6. String anInterface = serviceConfig.getInterface();
  7. String group = serviceConfig.getGroup();
  8. if (!inf.equalsIgnoreCase(anInterface)) {
  9. continue;
  10. }
  11. if (!vVersion.equalsIgnoreCase(version)) {
  12. continue;
  13. }
  14. if (uGroup != null && !group.equalsIgnoreCase(uGroup)) {
  15. continue;
  16. }
  17. ref = serviceConfig.getRef();
  18. break;
  19. }
  • 利用SpringMVC的的处理机制将Controller移除
  1. try {
  2. HttpServletRequest req = (HttpServletRequest) servletRequest;
  3. HttpServletResponse resp = (HttpServletResponse) servletResponse;
  4. ServletInvocableHandlerMethod invocableMethod = new ServletInvocableHandlerMethod(handlerMethod);
  5. WebDataBinderFactory binderFactory = getDataBinderFactory(invocableMethod);
  6. invocableMethod.setDataBinderFactory(binderFactory);
  7. ServletWebRequest webRequest = new ServletWebRequest(req, resp);
  8. ModelAndViewContainer mavContainer = new ModelAndViewContainer();
  9. HandlerMethodArgumentResolverComposite handlerMethodArgumentResolverComposite = new HandlerMethodArgumentResolverComposite();
  10. handlerMethodArgumentResolverComposite.addResolvers(getDefaultArgumentResolvers());
  11. invocableMethod.setHandlerMethodArgumentResolvers(handlerMethodArgumentResolverComposite);
  12. List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
  13. HandlerMethodReturnValueHandlerComposite returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
  14. invocableMethod.setHandlerMethodReturnValueHandlers(returnValueHandlers);
  15. invocableMethod.invokeAndHandle(webRequest, mavContainer);
  16. methodMap.put(handlerMethod, invocableMethod);
  17. } catch (Exception e) {
  18. fail(servletResponse, e);
  19. }
  • 如何突破传递参数的问题。
    利用SpringMVC的 HandlerMethodArgumentResolver 即可解析
    到这一步,http转dubbo就处理好了,测试发现SpringMVC在3,4,5的几个大版本中稍有变动,兼容花费一点时间。后续跟新会上传的github。 :)

结论

Spring很强大. 代码地址 dubbo-invoke

2019-09-22