0°

SpringCloud服务网关Zuul

本篇概述

最近在忙着网站改版,所以博客的更新就中断了,最近特意抽出了点时间继续更新SpringCloud相关的内容。下面我们言归正传。来介绍本篇的内容也就是SpringCloud中的服务网关组件Zuul。在之前文章中介绍过,SpringCloud中每一个组件都是为了解决某些问题的而存在的。例如注册中心、配置中心等等。那么Zuul组件到底是为了解决什么样的问题的呢?我们知道接口在对外供提服务时是需要对接口进行相应的验证的。如果对外提供的服务非常多,那么这些接口都需要进行相同的验证。但这明显不太方便。因为会造成大量重复的代码。但这时可能有人会说我们可以直接写一个拦截器拦截所有的请求,然后在拦截器里做这些接口验证的功能。但这里有一个弊端就是拦截器必须写在同一个服务中,否则拦截器会拦截不到所有的请求。不过通常公司对外提供服务时,会是有多个服务的,如果要采用拦截器的方式,也是需要要写多个拦截器。所以这样的方式,还是没有解决上述的问题。这时服务网关就诞生了。所有的请求都先请求网关,然后在由网关进行相应的服务调用。比较常见的网关服务例如大名鼎鼎的Nginx。但Nginx只是提供了相应的配置,如果我们想要用Nginx来实现我们自己的扩展的功能,那么我们就要使用Lua语言开发,但这明显不方便,因为我们根本不会Lua语言。至少我不会。SpringCloud为了降低服务网关的使用门槛,于是提供了Zuul组件,因为它是用Java开发的,所以在SpringCloud中使用非常方便。下面我们详细介绍一下Zuul相关的内容。为了方便演示Zuul相关的内容,我们创建一个注册中心、一个服务提供方。因为这些内容之前已经介绍过了,我们就不在做过多的介绍了,我们直接看一下怎么创建Zuul组件。


创建Zuul

  • 在IDEA中选择Spring Initializr选项。   
  • 设置项目的相关参数,在这一点和创建SpringBoot的项目没有任何区别。
  • 选择Zuul组件需要的依赖。
  • 这一步直接点击完成就可以了。 

这样我们就基本上创建完了Zuul组建了。下面我们配置一下项目相关的参数,也就是注册中心。

application.yml:

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka/
spring:
  application:
    name: springcloud-zuul
server:
  port: 8081

Zuul的使用

因为Zuul组件的作用是做服务网关的,也就是接口的统一入口。所以为了演示方便,我们新创建一个服务提供方,然后用Zuul来进行调用。下面为服务提供方项目配置及其Controller接口代码。

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka/
spring:
  application:
    name: springcloud-api
server:
  port: 8080
package com.jilinwula.springcloud.api.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/api")
public class ApiController {

    @GetMapping("/get")
    public Object get() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code", 0);
        map.put("msg", "success");
        map.put("data", "吉林乌拉");
        return map;
    }
}

下面我们通过http文件类型的方式来调用一下上述的接口,看看该接口是否能访问成功,在访问之前,我们别忘记了,要先启动注册中心服务。下面我们访问一下上面的接口。

GET http://127.0.0.1:8080/api/get

返回的结果:

GET http://127.0.0.1:8080/api/get

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 14 Apr 2019 08:45:56 GMT

{
  "msg": "success",
  "code": 0,
  "data": "吉林乌拉"
}

Response code: 200; Time: 13ms; Content length: 40 bytes

添加@EnableZuulProxy注解

下面我们通过Zuul来访问一下上述接口。但在访问之前,我们还是和之前介绍注册中心或配置中心一样,均需要在项目的启动类上添加相应的注解。在SpringCloud中Zuul组件需要添加@EnableZuulProxy注解。下面为Zuul项目的启动类源码。

package com.jilinwula.springcloud.zuul;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
@EnableZuulProxy
public class SpringcloudZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudZuulApplication.class, args);
    }

}

下面启动一下Zuul项目。如果项目启动不抛出任何异常时,则表示该项目启动成功,那我们怎么能通过Zuul来调用其它项目的服务呢?在这里有一个前提,就是Zuul项目和需要调用的项目必须都在注册中心中注册。否则Zuul是调用不了其它项目服务的。下面我们访问一下注册中心地址,来看看接口的提供方和Zuul服务是否已经成功的在注册中心注册成功。下面为注册中心的默认访问地址:

http://127.0.0.1:8761/

返回的结果:

title

Zuul默认路由规则

我们看这两个服务都注册成功了。下面我们介绍一下Zuul访问其它服务的规则。通过Zuul访问其它服务非常的简单,我们只需要在要访问的接口前面加上该服务在注册中心中注册的名字即可。例如我们刚刚访问的接口地址为http://127.0.0.1:8080/api/get。而该服务在注册中心的注册名字为springcloud-api。所以我们通过Zuul访问上述接口地址为:http://127.0.0.1:8081/springcloud-api/api/get。下面我们直接访问上述接口,来看一下,是否能够成功的访问上述接口的数据。

http://127.0.0.1:8081/springcloud-api/api/get

返回结果:

GET http://127.0.0.1:8081/springcloud-api/api/get

HTTP/1.1 200 
Date: Sun, 14 Apr 2019 09:13:26 GMT
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked

{
  "msg": "success",
  "code": 0,
  "data": "吉林乌拉"
}

Response code: 200; Time: 21ms; Content length: 40 bytes

Zuul自定义路由规则

我们看成功的返回了其它服务的接口信息了。这就是Zuul组件的路由的功能。但上面的路由规则是默认的访问规则。下面我们介绍一下自定义的路由访问规则。在SpringCloud中Zuul自定义路由访问规则也非常的简单,只需要在Zuul服务的配置文件中添加相应的配置即可。下面详细配置信息。

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka/
spring:
  application:
    name: springcloud-zuul
server:
  port: 8081
zuul:
  routes:
    openapi:
      path: /openapi/**
      serviceId: springcloud-api

这样我们可以访问openapi路径来访问springcloud-api服务了。下面我们验证一下:

GET http://127.0.0.1:8081/openapi/api/get

返回的结果:

GET http://127.0.0.1:8081/openapi/api/get

HTTP/1.1 200 
Date: Sun, 14 Apr 2019 09:41:42 GMT
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked

{
  "msg": "success",
  "code": 0,
  "data": "吉林乌拉"
}

Response code: 200; Time: 23ms; Content length: 40 bytes

我们看这样,我们就可以用openapi来访问其它服务了。但上述的配置不会影响Zuul组件的默认路由配置,我们依然还可以用服务的名字来进行服务调用。

GET http://127.0.0.1:8081/springcloud-api/api/get

返回结果:

GET http://127.0.0.1:8081/springcloud-api/api/get

HTTP/1.1 200 
Date: Sun, 14 Apr 2019 09:45:56 GMT
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked

{
  "msg": "success",
  "code": 0,
  "data": "吉林乌拉"
}

Response code: 200; Time: 16ms; Content length: 40 bytes

上述的功能就是Zuul组件的自定义路由功能,非常的简单。但上面的配置还可以优化。下面为优化后的配置。

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka/
spring:
  application:
    name: springcloud-zuul
server:
  port: 8081
zuul:
  routes:
    springcloud-api: /openapi/**

Zuul禁用某服务

下面我们介绍一下怎么通过Zuul来禁用某个服务,既然Zuul可以路由到其它服务,那同样Zuul依然可以禁用某个服务。下面我们看一下具体的配置。

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka/
spring:
  application:
    name: springcloud-zuul
server:
  port: 8081
zuul:
  routes:
    springcloud-api: /openapi/**
  ignored-patterns:
    - /springcloud-api/api/get

通过上述的配置我们禁用了Zuul组件的默认路由访问规则。下面我们在访问一下上述接口,看一下还能否成功的返回接口信息。

GET http://127.0.0.1:8081/springcloud-api/api/get

返回结果:

GET http://127.0.0.1:8081/springcloud-api/api/get

HTTP/1.1 404 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 14 Apr 2019 10:03:34 GMT

{
  "timestamp": "2019-04-14T10:03:34.645+0000",
  "status": 404,
  "error": "Not Found",
  "message": "No message available",
  "path": "/springcloud-api/api/get"
}

Response code: 404; Time: 15ms; Content length: 144 bytes

我们看该接口直接返回404错误了。这也就说明,我们禁用某接口的配置成功了。如果此时我们继续访问自定义的路由规则,则该接口还会调用成功。因为我们并没有禁用该接口。

GET http://127.0.0.1:8081/openapi/api/get

返回结果:

GET http://127.0.0.1:8081/openapi/api/get

HTTP/1.1 200 
Date: Sun, 14 Apr 2019 10:07:36 GMT
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked

{
  "msg": "success",
  "code": 0,
  "data": "吉林乌拉"
}

Response code: 200; Time: 16ms; Content length: 40 bytes

Zuul过滤器功能

这就是Zuul组件禁用某接口相关的配置。上述功能就是Zuul组件的路由功能。在实际开发中,我们可以将服务网关,也就Zuul服务对外暴露,而禁止其它服务对外访问,这样所有接口的请求都会通过Zuul来访问,这样我们就可以在服务网关中进行相应的服务验证了,而被调用的接口服务则不需要做任何更改。下面我们介绍一下Zuul组件的验证功能。


前置过滤器

在SpringCloud中Zuul组件除了提供路由的功能外,还提供了服务验证的功能,其本质是采用过滤器实现的。在Zuul中主要分为前置过滤器和后置过滤器。下面我们分别来介绍一下怎么配置上述的过滤器。我们首先看一下前置过滤器。配置前置过滤器非常的简单,因为Zuul组建已经为我们封装好了这些过滤器。我们只需要新创建一个普通的类,然后实现相应的过滤器类方法即可。下面为具体的代码。

package com.jilinwula.springcloud.zuul.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;


@Component
public class TokenFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        return null;
    }
}

Zuul过滤器方法介绍

下面我们详细介绍一下上述代码中方法的作用:

  • filterType: 指定该过滤器的类型,也就是前置过滤器还是后置过滤器等。
  • filterOrder: 指定该过滤器过滤顺序,值越小,优先级越高。
  • shouldFilter: 指定该过滤器是否有效,true为有效,false为无效。
  • run: 指定该过滤器的具体逻辑。

下面我们来做一个简单的参数验证,也就是所有请求的参数中都必须带有token参数,并且参数值为jilinwula。否则则提示接口调用失败。下面我们将上述需求的逻辑写到run方法中。下面我详细的代码。

package com.jilinwula.springcloud.zuul.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;


@Component
public class TokenFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        String token = request.getParameter("token");
        if (StringUtils.isEmpty(token) || "jilinwula".equals(token)) {
            requestContext.setSendZuulResponse(false);
            requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
        }
        return null;
    }
}

下面我们验证一下上面的代码是否能验证成功。我们首先访问不带token参数的接口看一下接口的返回结果。

GET http://127.0.0.1:8081/openapi/api/get

返回结果:

GET http://127.0.0.1:8081/openapi/api/get

HTTP/1.1 401 
Content-Length: 0
Date: Sun, 14 Apr 2019 11:14:04 GMT

<Response body is empty>

Response code: 401; Time: 13ms; Content length: 0 bytes

我们看成功的返回了我们在过滤器中run方法里返回的错误码。下面我们访问一下正确的接口参数,来看一下能否成功的返回接口数据。

GET http://127.0.0.1:8081/openapi/api/get?token=jilinwula

返回结果:

GET http://127.0.0.1:8081/openapi/api/get?token=jilinwula

HTTP/1.1 200 
Date: Sun, 14 Apr 2019 11:16:22 GMT
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked

{
  "msg": "success",
  "code": 0,
  "data": "吉林乌拉"
}

Response code: 200; Time: 17ms; Content length: 40 bytes

后置过滤器

我们看这样就成功的返回了服务的接口数据了。在实际的开发中,我们可以使用Zuul中的前置过滤器来实现服务的验证功能。下面我们介绍一下后置过滤器。后置过滤器和前置过滤器一样,唯一的不同就是后置过滤器是在接口返回的数据之后进行相应的处理的。下面我们看一下具体的代码。

package com.jilinwula.springcloud.zuul.filter;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.util.JSONPObject;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Component
public class ResponseFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return FilterConstants.POST_TYPE;
    }

    @Override
    public int filterOrder() {
        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletResponse response = requestContext.getResponse();
        response.setHeader("secret_key","jilinwula");
        return null;
    }
}

上面的代码非常的简单的,我们只是在接口返回的Header中添加了一个secret_key参数,下面我们验证一下该后置过滤器是否有效。

GET http://127.0.0.1:8081/openapi/api/get?token=jilinwula

返回结果:

GET http://127.0.0.1:8081/openapi/api/get?token=jilinwula

HTTP/1.1 200 
secret_key: jilinwula
Date: Sun, 14 Apr 2019 13:24:33 GMT
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked

{
  "msg": "success",
  "code": 0,
  "data": "吉林乌拉"
}

Response code: 200; Time: 27ms; Content length: 40 bytes

我们看返回的结果中成功返回了后置过滤器的参数。下面我们请求一下错误的接口,也就是参数不正确的接口,看一下返回的结果是什么。

GET http://127.0.0.1:8081/openapi/api/get

返回结果:

GET http://127.0.0.1:8081/openapi/api/get

HTTP/1.1 401 
secret_key: jilinwula
Content-Length: 0
Date: Sun, 14 Apr 2019 13:27:53 GMT

<Response body is empty>

Response code: 401; Time: 15ms; Content length: 0 bytes

因为现在我们有两个过滤器。又因为上述接口中没有添加token参数,所以该接口不会成功返回接口的信息。但我们看依然返回了后置过滤器中的参数。


上述内容就是本篇的全部内容,非常的简单,如有不了解的欢迎留言。谢谢

项目源码

https://github.com/jilinwula/jilinwula-springcloud-zuul

0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论