routing zuul_Zuul核心逻辑浅析

本文深入解析Spring Cloud Zuul的核心逻辑,解释了Zuul作为Servlet如何处理请求并使用Filter进行预处理、路由和后处理。Zuul通过RequestContext在过滤器间传递信息,实现请求转发和响应回传。文章还介绍了如何自定义ZuulServlet,实现类似的功能。
摘要由CSDN通过智能技术生成

什么是网关?为什么需要使用网关?

其中一个很重要的点:在不使用网关的情况下,服务是直接暴露给服务调用方。当调用方增多,势必需要添加定制化访问权限、校验等逻辑。当添加API网关后,在第三方调用端和服务提供方之间就创建了一面墙,这面墙直接与调用方通信进行权限控制。

b2a45179bc0f2f26d63555d3f236dda7.png

网关将发送给网关的请求转移到真实的业务系统进行处理,实际业务系统处理完成后,将返回的结果回传给网关,由网关返回给调用方,整个过程对调用方而言是无感的,调用方只需要关注自己跟网关之间的协作关系,由网关在内部进行了一系列的处理。

了解http请求过程的大概都知道,一个http请求会携带请求头和请求体,然后服务端基于请求头和请求体数据,处理请求,生成响应头和响应体结果返回给客户端。既然网关的目的是将接收的请求转移到真实的业务系统,那么自然需要将网关接收到的数据(包括请求头和请求体)传递给真实的业务系统,业务系统接收到携带正确数据的请求后,调用相应的业务逻辑进行处理。业务系统处理完成后,返回的依然是业务系统的Response数据,那这部分数据如何通过网关回传给用户呢?同样回到http请求的本质,http请求处理后会有相应的响应数据,这个响应数据包含响应头和响应体,因此,网关系统需要拿到真实业务系统的处理结果的响应头和响应体后回传给调用方,整个逻辑完成了一个网关的处理流程。

5df273f58a6e701847367106eac5eb36.png

那么,Spring Cloud Zuul是怎么设计的呢? 

Spring Cloud官方对zuul有一些简单的描述(Spring Cloud的文档写的其实很一般 ),本文侧重讲述zuul网关实现的核心逻辑,我们重点关注:Zuul Developer Guide:

Spring Cloud Zuul

https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.1.5.RELEASE/single/spring-cloud-netflix.html

8.18 Zuul Developer Guide

8.18.1 The Zuul Servlet

Zuul is implemented as a Servlet. For the general cases, Zuul is embedded into the Spring Dispatch mechanism. This lets Spring MVC be in control of the routing. In this case, Zuul buffers requests. If there is a need to go through Zuul without buffering requests (for example, for large file uploads), the Servlet is also installed outside of the Spring Dispatcher. By default, the servlet has an address of /zuul. This path can be changed with the zuul.servlet-path property.

Zuul实现了Servlet。对于一般情况,Zuul已嵌入到Spring Dispatch机制中,这使得Spring MVC可以控制路由。在使用中,Zuul会缓冲请求。如果需要在不缓冲请求的情况下进行Zuul操作(例如,对于大文件上传),Servlet可以部署在Spring Dispatcher的外部。

8.18.2 Zuul RequestContext

To pass information between filters, Zuul uses a RequestContext. Its data is held in a ThreadLocal specific to each request. Information about where to route requests, errors, and the actual HttpServletRequest and HttpServletResponse are stored there. The RequestContext extends ConcurrentHashMap, so anything can be stored in the context. FilterConstants contains the keys used by the filters installed by Spring Cloud Netflix (more on these later).

为了在过滤器之间传递信息,Zuul使用了RequestContext,它的数据保存在特定于每个请求的ThreadLocal中。RequestContext扩展了ConcurrentHashMap,因此任何内容都可以存储在上下文中。 

从上面的Developer Guide我们能够很清楚的了解到:zuul本质上是有一个核心的Servlet,多个Filter对请求进行拦截处理,这些Filter之间是通过RequestContext进行数据的传递。

本文将先分析Spring Cloud Zuul的设计思想,然后着重以代码的方式实现Zuul的核心逻辑。

1. Spring Cloud Zuul业务流程 

e458467a84d19ae9f557768f63023201.png

结合Spring Cloud Zuul的Developer Guide和上面的业务流程图,我们可以得到以下的结论:Zuul实现网关的核心逻辑是Servlet接收到Http Request请求后,会经过一系列的Filter进行串联操作,从"pre" filters到“routing” filters,再到"post" filters完成整个网关请求的转接和回传操作。 在这个过程中,发生了异常行为的时候,会调用"error" filters进行错误的处理逻辑。

下面我们试着去代码中找寻对应的Servlet,作为入口进行深入理解Zuul 。

Spring Cloud Zuul本身是依赖于Netflix Zuul,Spring Cloud基于Auto Configuration机制注册Netflix Zuul,因此,我们把重点放在Netflix zuul上。

2. Spring Cloud Zuul(Netflix Zuul)的代码实现逻辑

了解一个框架最直接的方法是去github上搜索这个框架,详细阅读这个框架的wiki文档,GitHub上对应的Netflix Zuul地址如下(本文从Netflix zuul 1.x版本入手):

Netflix Zuul

https://github.com/Netflix/zuul/wiki/Getting-Started
    <groupId>com.netflix.zuulgroupId>     zuul-core</artifactId>     1.0.0version> dependency> 

在阅读wiki文档中,我重点关注了How To Use这个栏目,结合最原始的使用,以了解Netflix Zuul的构造逻辑。

zuul-simple-webapp这个页面下,有一句很直观的说明:

ZuulServlet is a servlet that matches all requests. It performs the core Zuul Filter flow of executing pre, routing, and post Filters.

ZuulServlet是一个匹配所有请求的servlet。它依次执行pre,routing和post过滤器的流程。

<servlet>     <servlet-name>Zuulservlet-name>     <servlet-class>com.netflix.zuul.http.ZuulServletservlet-class> servlet> <servlet-mapping>     <servlet-name>Zuulservlet-name>     <url-pattern>/*url-pattern> servlet-mapping> 

因此,本文选择从com.netflix.zuul.http.ZuulServlet作为入口学习netflix zuul。

com.netflix.zuul.http.ZuulServlet继承了javax.servlet.http.HttpServlet,它本身是一个标准的Servlet。我们知道Servlet处理请求的逻辑主要是com.netflix.zuul.http.ZuulServlet#service(ServletRequest request,ServletResponse response)方法,因此,侧重观察service()方法的执行过程有助于我们熟悉整个zuul的调用逻辑,进而对netflix zuul的实现核心进行充分的认识,为我们后面的自定义的网关提供思路。

结合service()的执行逻辑,我们可以得到ZuulServlet处理http请求的调用链:

d66dbb5d02b33ca38ecef6c785bbcd8b.png

在这个执行过程中,需要重点关注com.netflix.zuul.context.RequestContext这个类,那这个类是用来做什么的呢?

回归到网关的本质,网关的目的是将请求从网关系统转移到真实的业务系统,以及包装业务系统返回的处理结果,回传给客户端。很明显,网关系统和业务系统是两个独立的系统,前文详细描述过在http请求的过程中,网关系统需要包装相应的请求头和请求体数据到业务系统以及将业务系统的数据包装后回传到调用端,因此,请求信息和响应信息必须需要一个介质去进行传递。在java中,线程本地变量能够很好的解决这个问题,因为每个请求的线程本地变量是相对独立的。

com.netflix.zuul.context.RequestContext

public class RequestContext extends ConcurrentHashMap<String, Object> {
        private static final Logger LOG = LoggerFactory.getLogger(RequestContext.class);    protected static Class extends RequestContext> contextClass = RequestContext.class;    private static RequestContext testContext = null;    protected static final ThreadLocal extends RequestContext> threadLocal = new ThreadLocal() {
            @Override        protected RequestContext initialValue() {
                try {
                    return contextClass.newInstance();            } catch (Throwable e) {
                    throw new RuntimeException(e);            }        }    };    public RequestContext() {
            super();    }    /**     * Override the default RequestContext     *     * @param clazz     */    public static void setContextClass(Class extends RequestContext> clazz) {
            contextClass = clazz;    }   .......     /**     * Get the current RequestContext     *     * @return the current RequestContext     */    public static RequestContext getCurrentContext() {
            if (testContext != null) return testContext;        RequestContext context = threadLocal.get();        return context;    }    /**     * Convenience method to return a boolean value for a given key     *     * @param key     * @return true or false depending what was set. default is false     */    public boolean getBoolean(String key) {
            return getBoolean(key, false);    }    /**     * Convenience method to return a boolean value for a given key     *     * @param key     * @param defaultResponse     * @return true or false depending what was set. default defaultResponse     */    public boolean getBoolean(String key, boolean defaultResponse) {
            Boolean b = (Boolean) get(key);        if (b != null) {
                return b.booleanValue();        }        return defaultResponse;    }    /**     * sets a key value to Boolen.TRUE     *     * @param key     */    public void set(String key) {
            put(key, Boolean.TRUE);    }    /**     * puts the key, value into the map. a null value will remove the key from the map     *     * @param key     * @param value     */    public void set(String key, Object value) {
            if (value != null) put(key, value);        else remove(key);    }    /**     * @return the HttpServletRequest from the "request" key     */    public HttpServletRequest getRequest() {
            return (HttpServletRequest) get("request");    }    /**     * sets the HttpServletRequest into the "request" key     *     * @param request     */    public void setRequest(HttpServletRequest request) {
            put("request", request);    }    /**     * @return the HttpServletResponse from the "response" key     */    public HttpServletResponse getResponse() {
            return (HttpServletResponse) get("response");    }    /**     * sets the "response" key to the HttpServletResponse passed in     *     * @param response     */    public void setResponse(HttpServletResponse response) {
            set("response", response);    }    /**     * sets the "responseBody" value as a String. This is the response sent back to the client.     *     * @param body     */    public void setResponseBody(String body) {
            set("responseBody", body);    }    /**     * @return the String response body to be snt back to the requesting client     */    public String getResponseBody() {
            return (String) get("responseBody");    }    /**     * sets the InputStream of the response into the responseDataStream     *     * @param responseDataStream     */    public void setResponseDataStream(InputStream responseDataStream) {
            set("responseDataStream", responseDataStream);    }    /**     * sets the flag responseGZipped if the response is gzipped     *     * @param gzipped     */    public void setResponseGZipped(boolean gzipped) {
            put("responseGZipped", gzipped);    }    /**     * @return true if responseGZipped is true (the response is gzipp
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值