第06课:服务网关

本文,咱们将学习 Spring Cloud的另外一个组件:zuul,它提供微服务的网关功能,即中转站,经过它提供的接口,能够转发不一样的服务。在学习 zuul 以前,咱们先接着上一篇的代码,来看看服务提供者是如何提供服务的。spring

在服务提供者的 module 下建立 HelloController 类,添加内容以下:api

@RestController
public class HelloController {

    @RequestMapping("index")
    public String index(){
        return "Hello World!";
    }
}

而后分别启动服务注册中心和服务提供者,浏览器输入:http://localhost:8762/index,便可看见以下画面:
图片描述浏览器

在实际的项目中,一个项目可能会包含不少个服务,每一个服务的端口和 IP 均可能不同。那么,若是咱们以这种形式提供接口给外部调用,代价是很是大的。从安全性上考虑,系统对外提供的接口应该进行合法性校验,防止非法请求,若是按照这种形式,那每一个服务都要写一遍校验规则,维护起来也很麻烦。安全

这个时候,咱们须要统一的入口,接口地址所有由该入口进入,而服务只部署在局域网内供这个统一的入口调用,这个入口就是咱们一般说的服务网关。服务器

Spring Cloud 给咱们提供了这样一个解决方案,那就是 zuul,它的做用就是进行路由转发、异常处理和过滤拦截。下面,我将演示若是使用 zuul 建立一个服务网关。app

建立 gateway 工程负载均衡

在父项目上右键 -> New -> Module,建立一个名为 gateway 的工程,在其 pom.xml 中,加入以下依赖:ide

<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
        </dependency>
    </dependencies>

建立 Application 启动类,并增长 @EnableZuulProxy 注解:微服务

@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class Application {

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

最后添加 application.yml 配置文件,内容以下:post

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 8080
spring:
  application:
    name: gateway
zuul:
  routes:
    api:
      path: /api/**
      serviceId: eurekaclient

咱们能够看到,服务网关的配置多了几项,具体含义以下。

  • zuul.routes.api.path:指定请求基础地址,其中 API 能够是任何字符。
  • serviceId:转发到的服务 ID,也就是指定服务的 application.name,上述实例的含义表示只要包含 /api/
    的地址,都自动转发到 eurekaclient 的服务去。

而后咱们启动服务注册中心、服务提供者、服务网关,访问地址:http://localhost:8080/api/index,咱们能够看到和以前的界面彻底同样。其实只要引入了 zuul,它就会自动帮咱们实现反向代理和负载均衡。配置文件中的地址转发其实就是一个反向代理,那它如何实现负载均衡呢?

咱们修改服务提供者的 Controller 以下:

RestController
public class HelloController {

    @Value("${server.port}")
    private int port;

    @RequestMapping("index")
    public String index(){
        return "Hello World!,端口:"+port;
    }
}

从新启动。而后再修改服务提供者的端口为8673,再次启动它(切记:原先启动的不要中止),访问地址:http://localhost:8761,咱们能够看到 eurekaclient 服务有两个地址:
图片描述
再不断访问地址:http://localhost:8080/api/index,能够看到交替出现如下界面:
图片描述

图片描述

由此能够得出,当一个服务启动多个端口时,zuul 服务网关会依次请求不一样端口,以达到负载均衡的目的。

服务拦截

前面咱们提到,服务网关还有个做用就是接口的安全性校验,这个时候咱们就须要经过 zuul 进行统一拦截,zuul 经过继承过滤器 ZuulFilter 进行处理,下面请看具体用法。

新建一个类 ApiFilter 并继承 ZuulFilter:

@Component
public class ApiFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

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

    @Override
    public Object run() {
        //这里写校验代码
        return null;
    }
}

其中:

  • filterType 为过滤类型,可选值有
    pre(路由以前)、routing(路由之时)、post(路由以后)、error(发生错误时调用)。
  • filterOrdery 为过滤的顺序,若是有多个过滤器,则数字越小越先执行
  • shouldFilter 表示是否过滤,这里能够作逻辑判断,true 为过滤,false 不过滤
  • run 为过滤器执行的具体逻辑,在这里能够作不少事情,好比:权限判断、合法性校验等。

下面,咱们来作一个简单的安全验证:

@Override
    public Object run() {
        //这里写校验代码
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        String token = request.getParameter("token");
        if(!"12345".equals(token)){
            context.setSendZuulResponse(false);
            context.setResponseStatusCode(401);
            try {
                context.getResponse().getWriter().write("token is invalid.");
            }catch (Exception e){}
        }
        return null;
    }

启动 gateway,在浏览器输入地址:http://localhost:8080/api/index,能够看到如下界面:
图片描述

再经过浏览器输入地址:http://localhost:8080/api/index?token=12345,能够看到如下界面:
图片描述

错误拦截

在一个大型系统中,服务是部署在不一样的服务器下面的,咱们不免会遇到某一个服务挂掉或者请求不到的时候,若是不作任何处理,服务网关请求不到会抛出500错误,对用户是不友好的。

咱们为了提供用户的友好性,须要返回友好性提示,zuul 为咱们提供了一个名叫 ZuulFallbackProvider 的接口,经过它咱们就能够对这些请求不到的服务进行错误处理。

新建一个类 ApiFallbackProvider 而且实现 ZuulFallbackProvider 接口:

Component
public class ApiFallbackProvider implements ZuulFallbackProvider{

    @Override
    public String getRoute() {
        return "eurekaclient";
    }

    @Override
    public ClientHttpResponse fallbackResponse() {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return 200;
            }

            @Override
            public String getStatusText() throws IOException {
                return "{code:0,message:\"服务器异常!\"}";
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream(getStatusText().getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }

其中,getRoute 方法返回要处理错误的服务名,fallbackResponse 方法返回错误的处理规则。

如今开始测试这部分代码,首先停掉服务提供者 eurekaclient,再重启 gateway,请求地址:http://localhost:8080/api/index?token=12345,便可出现如下界面:
图片描述

相关文章
相关标签/搜索