适用场景

考虑如下场景:
image

  1. 存在服务A服务B
  2. 服务A服务B都有各自的REST API
  3. 用户只能访问服务A, 如果要访问服务B,则需通过服务A来访问

针对这个场景,可能大家都会想到应该用Nginx来做, 简单快捷。事实也确实如此, 如果仅仅是做请求转发,完全可以通过Nignx

继续加条件:

  1. 调用 服务B 的API 需要签名校验;
  2. 服务A服务B 的 REST API 路径没有规律;

这时候如果继续用Nginx, 那么会出现如下问题:

  1. Nginx无法调用服务B时候,无法生成并携带签名(当然其他方式比如通过Openresty + Lua 可以做到, 但相对要复杂,不在本文讨论范围之内)

  2. 服务A服务B的路径没有规律,意味着我们判断将请求转发给服务A还是服务B, 只能将 服务A的所有接口都罗列在nignx配置文件上,匹配则转发到服务A, 否则转发到服务B。 配置文件类似:

     location /data/query1/list {
          proxy_pass http://服务A;	
     }
    
     location /data/query1/list2 {
          proxy_pass http://服务A;	
     } 
    
     location /data/query/list2 {
          proxy_pass http://服务A;	
     }
    
     .....
    

比较臃肿繁琐,并且后期服务A加了接口之后,也需要修改Nginx配置文件, 容易遗漏产生问题。

这种情况下,我们用代码来转发请求就比较合适。

Java实现

需求

根据上面的场景,服务A需要具备如下功能:

  1. 如果用户请求的是服务A本身的接口, 那么执行服务A的接口逻辑;
  2. 如果用户请求的不是服务A的接口,那么服务A就需要调用服务B的接口,然后将服务B的响应流写入到服务A对用户的响应流中。

实现 - 需求1

利用SpringBoot(或Spring MVC), 我们可以很轻松的实现第一点功能。

我们在服务A中添加如下代码:

@RequestMapping({"/**"})
public void proxy(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    proxyServlet.service(request, response);
}

我们仅需 @RequestMapping({"/**"}), 就可以做到,如果请求是服务A本身的具有的接口, 那么就不会走我们的proxy方法; 否则就会走我们的proxy方法, 去请求服务B

实现 - 需求2

上面代码中的 proxyServlet, 封装了请求服务B的代码。 也就是承担着第二点需求的功能。这个 ProxyServlet 我们直接采用 开源项目 HTTP-Proxy-Servlet 来实现。

具体的用法,参考 HTTP-Proxy-ServletREADME文档,很清楚。 这里就不在赘述。基本思路是在ProxyController (就是包含上面proxy方法的controller) 初始化以后, 手动构造一个ProxyServlet作为全局变量即可。如果需要特殊的签名,也可以通过更改ProxyServlet的源码来实现,非常简单。

至此,上述问题完美解决。

Q.E.D.