SpringCloud07—API网关服务:Spring Cloud Zuul

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-test

test

org.springframework.cloud

spring-cloud-starter-netflix-eureka-client

org.springframework.cloud

spring-cloud-starter-netflix-zuul

2.2.9.RELEASE

spring-cloud-starter-netflix-zuul依赖,可以通过查看它的依赖内容了解到:

该模块中不仅包含了Netflix Zuul的核心依赖zuul-core,它还包含了下面这些网关服务需要的重要依赖。

  • spring-cloud-starter-hystrix: 该依赖用来在网关服务中实现对微服务转发时候的保护机制,通过线程隔离和断路器,防止微服务的故障引发API网关资源无法释放,从而影响其他应用的对外服务。

  • spring-cloud-starter-ribbon: 该依赖用来实现在网关服务进行路由转发时候的客户端负载均衡以及请求重试。

  • spring-boot-starter-actuator: 该依赖用来提供常规的微服务管理端点。另外,在Spring Cloud Zuul中还特别提供了/routes端点来返回当前的所有路由规则。

2.在主应用类上添加注解

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication

@EnableZuulProxy

@EnableEurekaClient

public class ApiGatewayApplication {

public static void main(String[] args) {

SpringApplication.run(ApiGatewayApplication.class, args);

}

}

使用@EnablezuulProxy注解开启Zuul的API 网关服务功能

3.在application.properties中配置Zuul应用的基础信息

如应用名、服务端口号等,具体内容如下:

server.port=5555

spring.application.name=api-getway

注册服务的时候使用服务的ip地址

eureka.instance.prefer-ip-address=false

eureka.client.service-url.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/

前缀,用来做版本控制

zuul.prefix=/v1

禁用默认路由,执行配置路由

zuul.ignored-services=“*”

7.2.2 请求路由

在application.properties添加以下配置就可以实现传统的路由转发功能

配置hello-service的服务路由

zuul.routes.api-a.path=/api-a/**

zuul.routes.api-a.service-id=hello-service

feign-consumer的服务路由

zuul.routes.api-b.path=/api-b/**

zuul.routes.api-b.service-id=feign-consumer

下面我大致解释一下以上代码的意思

在这里插入图片描述

  • http://localhost:5555/api-a/hello: 该url符合/api-a/**规则,由api-a路由负责转发,该路由映射的serviceld为hello-service,所以最终/hello 请求会被发送到hello-service服务的某个实例上去,也就是对应的/hello接口

在这里插入图片描述

  • http:/ /localhost:5555/api-b/feign-consumer: 该url符合/api-b/**规则,由api-b路由负责转发,该路由映射的serviceId为feign-consumer,所以最终/feign-consumer请求会被发送到feign-consumer服务的某个实例上去。

7.3 请求过滤


在实现了请求路由功能之后,我们的微服务应用提供的接口就可以通过统一的API网关入口被客户端访问到了。

但是,每个客户端用户请求微服务应用提供的接口时,它们的访问权限往往都有一定的限制,系统并不会将所有的微服务接口都对它们开放。然而,目前的服务路由并没有限制权限这样的功能,所有请求都会被毫无保留地转发到具体的应用并返回结果,为了实现对客户端请求的安全校验和权限控制,最简单和粗暴的方法就是为每个微服务应用都实现一套用于校验签名和鉴别权限的过滤器或拦截器。不过,这样的做法并不可取,它会增加日后系统的维护难度,因为同一个系统中的各种校验逻辑很多情况下都是大致相同或类似的,这样的实现方式会使得相似的校验逻辑代码被分散到了各个微服务中去,冗余代码的出现是我们不希望看到的。所以,比较好的做法是将这些校验逻辑剥离出去,构建出一个独立的鉴权服务。在完成了剥离之后,有不少开发者会直接在微服务应用中通过调用鉴权服务来实现校验,但是这样的做法仅仅只是解决了鉴权逻辑的分离,并没有在本质上将这部分不属于冗余的逻辑从原有的微服务应用中拆分出,冗余的拦截器或过滤器依然会存在。

对于这样的问题,更好的做法是通过前置的网关服务来完成这些非业务性质的校验。由于网关服务的加入,外部客户端访问我们的系统已经有了统一入口,既然这些校验与具体业务无关,那何不在请求到达的时候就完成校验和过滤,而不是转发后再过滤而导致更长的请求延迟。同时,通过在网关中完成校验和过滤,微服务应用端就可以去除各种复杂的过滤器和拦截器了,这使得微服务应用接口的开发和测试复杂度也得到了相应降低。

为了在API网关中实现对客户端请求的校验,我们将继续介绍Spring Cloud Zuul的另外一个核心功能:请求过滤

Zuul 允许开发者在API网关上通过定义过滤器来实现对请求的拦截与过滤,实现的方法非常简单,我们只需要继承ZuulFilter抽象类并实现它定义的4个抽象函数就可以完成对请求的拦截和过滤了

下面的代码定义了一个简单的Zuul过滤器,它实现了在请求被路由之前检查HttpServletRequest中是否有accessToken参数,若有就进行路由,若没有就拒绝访问,返回401 Unauthorized错误。

import com.netflix.zuul.ZuulFilter;

import com.netflix.zuul.context.RequestContext;

import com.netflix.zuul.exception.ZuulException;

import javax.servlet.http.HttpServletRequest;

@Component

public class AccessFilter extends ZuulFilter {

private final String tokenSign = “accessToken”;

private static Logger log= LoggerFactory.getLogger(AccessFilter.class);

@Override

public String filterType() {

return “pre”;

}

@Override

public int filterOrder() {

return 0;

}

@Override

public boolean shouldFilter() {

return true;

}

@Override

public Object run() throws ZuulException {

RequestContext ctx = RequestContext.getCurrentContext();

HttpServletRequest request = ctx.getRequest();

log.info(“send{} request to {}”, request.getMethod(), request.getRequestURL().toString());

Object accessToken = request.getHeader(tokenSign);

if (accessToken == null) {

log.warn(“token is empty”);

ctx.setSendZuulResponse(false);

ctx.setResponseStatusCode(401);

return null;

}

log.info(“token is Ok”);

return null;

}

}

在上面实现的过滤器代码中,我们通过继承ZuulFilter抽象类并重写下面4个方法来实现自定义的过滤器。这4个方法分别定义了如下内容:

  • filterType: 过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。这里定义为pre,代表会在请求被路由之前执行。

  • filterOrder: 过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来依次执行。

  • shouldFilter: 判断该过滤器是否需要被执行。这里我们直接返回了true,因此该过滤器对所有请求都会生效。实际运用中我们可以利用该函数来指定过滤器的有效范围。

  • run: 过滤器的具体逻辑。这里我们通过ctx.setsendzuulResponse(false),令zuul过滤该请求,不对其进行路由,然后通过ctx.setResponsestatus-Code (401)设置了其返回的错误码,当然也可以进一步优化我们的返回,比如,ctx.setResponseBody (body)对返回的 body内容进行编辑等。

重启项目,访问http://localhost:5555/v1/api-a/hello

在这里插入图片描述

由于需要在请求头设置token,所以我们该用postman进行测试

在这里插入图片描述

到这里,对于API网关服务的快速入门示例就完成了。通过对Spring Cloud Zuul两个核心功能的介绍,相信大家已经能够体会到API网关服务对微服务架构的重要性了,就目前掌握的API 网关知识,我们可以将具体原因总结如下:

  • 它作为系统的统一入口,屏蔽了系统内部各个微服务的细节。

  • 它可以与服务治理框架结合,实现自动化的服务实例维护以及负载均衡的路由转发。·它可以实现接口权限校验与微服务业务逻辑的解耦。

  • 通过服务网关中的过滤器,在各生命周期中去校验请求的内容,将原本在对外服务层做的校验前移,保证了微服务的无状态性,同时降低了微服务的测试难度,让服务本身更集中关注业务逻辑的处理。

7.4 Cookie与头信息


默认情况下,Spring Cloud Zuul在请求路由时,会过滤掉HtTP请求头信息中的一些敏感信息,防止它们被传递到下游的外部服务器。

默认的敏感头信息通过:zuul. sensitiveHeaders参数定义,包括Cookie、Set-Cookie、Authorization三个属性。

所以,我们在开发Web项目时常用的Cookie在SpringCloudZuul网关中默认是不会传递的,这就会引发一个常见的问题:如果我们要将使用了Spring Security、 Shiro等安全框架构建的Web应用通过SpringCloudZuul构建的网关来进行路由时,由于Cookie信息无法传递,我们的Web应用将无法实现登录和鉴权。为了解决这个问题,配置的方法有很多。

  • 通过设置全局参数为空来覆盖默认值保证通过网关访问服务时可以携带cookie,具体如下:

设置全局参数为空来覆盖默认值

zuul.sensitive-headers=

  • 对指定路由开启自定义敏感头

对指定路由开启自定义敏感头

zuul.routes..customSensitiveHeaders=true

  • 将指定路由的敏感头信息设置为空

zuul.routes..sensitiveHeaders=[这里设置要过滤的敏感头]

7.5 对于HyStrix和Ribbon的设置


Zuul天生就拥有线程隔离和断路器的自我保护功能,以及对服务调用的客户端负载均衡功能。

但是需要注意,当使用path与ur1的映射关系来配置路由规则的时候,对于路由转发的请求不会采用HystrixCommand来包装,所以这类路由请求没有线程隔离和断路器的保护,并且也不会有负载均衡的能力。因此,我们在使用Zuul的时候尽量使用path和serviceId的组合来进行配置,这样不仅可以保证API网关的健壮和稳定,也能用到Ribbon的客户端负载均衡功能。

  • 1.启用hystrix配置

hystrix.metrics.enabled=true

  • 2.设置API网关中路由转发请求的HystrixCommand执行超时时间,单位为毫秒

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=3000

当路由转发请求的命令执行时间超过该配置值之后,Hystrix 会将该执行命令标记为TIMEOUT并抛出异常,Zuul会对该异常进行处理并返回如下JSON信息给外部调用方。

{

“timestamp”: “2021-07-15T07:25:21.723+00:00”,

“status”: 504,

“error”: “Gateway Timeout”,

“message”: “”

}

  • 3.路由转发请求时,创建请求连接的超时时间 ,单位ms

ribbon.ConnectTimeout=3000

注意:当ribbon.ConnectTimeout的配置值小于hystrix.command.default.execution.isolation.thread. timeoutInMilliseconds配置值的时候,若出现路由请求出现连接超时,会自动进行重试路由请求,如果重试依

然失败,Zuul会返回如下JSON信息给外部调用方。

{

“timestamp”:1481352582852,

“status”:500,

“error”:“Internal Server Error”,

“exception”:“com. netflix. zuul. exception. ZuulException”,

“message”:“NUMBEROF RETRIES NEXTSERVER EXCEEDED”

}

如果ribbon .ConnectTimeout的配置值大于hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds配置值的时候,当出现路由请求连接超时时,由于此时对于路由转发的请求命令已经超时,所以不会进行重试路由请求,而是直接按请求命令超时处理,返回TIMEOUT的错误信息。

{

“timestamp”: “2021-07-15T07:42:51.028+00:00”,

“status”: 500,

“error”: “Internal Server Error”,

“message”: “”

}

  • 4.设置路由的重试机制 默认是true

zuul.retryable=false

指定路由的重试机制进行关闭

zuul.routes..retryable=false

注意:route就是你的服务名,比如zuul.routes.hello-service.retryable=false

7.6 异常处理


首先,我们尝试创建一个pre类型的过滤器,并在该过滤器的run方法实现中抛出一个异常。比如下面的实现,在run方法中调用的doSomething方法将抛出Runt imeException异常。

import com.netflix.zuul.ZuulFilter;

import com.netflix.zuul.exception.ZuulException;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Component;

@Component

public class ThrowExceptionFilter extends ZuulFilter {

private static Logger log = LoggerFactory.getLogger(ThrowExceptionFilter.class);

@Override

public String filterType() {

return “pre”;

}

@Override

public int filterOrder() {

return 0;

}

@Override

public boolean shouldFilter() {

return true;

}

@Override

public Object run() throws ZuulException {

log.info(“这是一个pre过滤器,将会抛出异常”);

this.doSomething();

return null;

}

private void doSomething() {

throw new RuntimeException("Exist some errors… ");

}

}

在添加了上面的过滤器之后,我们可以将该应用以及之前的相关应用运行起来,并根据网关配置的路由规则访问服务接口,比如http://localhost:5555/api-a/hello此时我们会发现,在API网关服务的控制台中输出了ThrowExceptionFilter的过滤逻辑中的日志信息。

在这里插入图片描述

但是返回给页面的信息中却拿不到我们想打印的信息

在这里插入图片描述

try-catch 处理

在catch异常的处理逻辑中并没有做任何输出操作,而是向请求上下文中添加了一些error相关的参数,主要

有下面三个参数。

  • error.status_code: 错误编码。

  • error.exception: Exception异常对象。

  • error.message: 错误信息。

其中,error.status_ code 参数就是SendErrorFilter过滤器用来判断是否需要执行的重要参数。分析到这里,实现异常处理的大致思路就开始明朗了,实现对ThrowExceptionFilter的run方法做一些异常处理的改造,具体如下:

@Component

public class ThrowExceptionFilter extends ZuulFilter {

private static Logger log = LoggerFactory.getLogger(ThrowExceptionFilter.class);

@Override

public String filterType() {

return “pre”;

}

@Override

public int filterOrder() {

return 0;

}

@Override

public boolean shouldFilter() {

return true;

}

@Override

public Object run() throws ZuulException {

log.info(“这是一个pre过滤器,将会抛出异常”);

RequestContext ctx = RequestContext.getCurrentContext();

try {

doSomething();

} catch (Exception e) {

e.printStackTrace();

ctx.set(“error.status_code”, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

ctx.set(“error.exception”, e);

}

return null;

}

private void doSomething() throws Exception {

throw new Exception("Exist some errors… ");

}

}

禁用过滤器

在Zuul中特别提供了-一个参数来禁用指定的过滤器,该参数的配置格式如下:

zuul...disable=true

比如

zuul.AccessFilter.pre.disable=true

7.7 服务的降级


import com.cloud.apigateway.filter.ThrowExceptionFilter;

import com.netflix.hystrix.exception.HystrixTimeoutException;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.http.HttpHeaders;

import org.springframework.http.HttpStatus;

import org.springframework.http.MediaType;

import org.springframework.http.client.ClientHttpResponse;

import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;

import java.io.InputStream;

@Component

public class FallBackConfig implements org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider {

private static Logger log = LoggerFactory.getLogger(FallBackConfig.class);

/**

  • 定义构造函数

**/

public ClientHttpResponse fallbackResponse() {

return response(HttpStatus.INTERNAL_SERVER_ERROR);

}

@Override

public String getRoute() {

return “*”;

}

@Override

public ClientHttpResponse fallbackResponse(String route, Throwable cause) {

// 捕获超时异常,返回自定义信息

if (cause instanceof HystrixTimeoutException) {

return response(HttpStatus.GATEWAY_TIMEOUT);

} else {

return fallbackResponse();

}

}

private ClientHttpResponse response(final HttpStatus status) {

return new ClientHttpResponse() {

@Override

public HttpStatus getStatusCode() {

return status;

}

@Override

public int getRawStatusCode() {

return status.value();

}

@Override

public String getStatusText() {

return status.getReasonPhrase();

}

@Override

public void close() {

log.warn(“连接服务关闭,无法进行访问”);

}

@Override

public InputStream getBody() {

String message =

“{\n” +

““code”: 500,\n” +

““message”: “微服务飞出了地球”\n” +

“}”;

return new ByteArrayInputStream(message.getBytes());

}

@Override

public HttpHeaders getHeaders() {

HttpHeaders headers = new HttpHeaders();

headers.setContentType(MediaType.APPLICATION_JSON);

return headers;

}

};

}

}

在这里插入图片描述

7.8 动态加载


在微服务架构中,由于API网关服务担负着外部访问统一入口的重任,它同其他应用不同,任何关闭应用和重启应用的操作都会使系统对外服务停止,对于很多7×24小时服务的系统来说,这样的情况是绝对不被允许的。所以,作为最外部的网关,它必须具备动态更新内部逻辑的能力,比如动态修改路由规则、动态添加/删除过滤器等。

通过Zuul实现的API网关服务当然也具备了动态路由和动态过滤器的能力。我们可以在不重启API网关服务的前提下,为其动态修改路由规则和添加或删除过滤器。下面我们分别来看看如何通过Zuul来实现动态API 网关服务。

7.8.1 动态路由

通过之前对请求路由的详细介绍,我们可以发现对于路由规则的控制几乎都可以在配置文件application.properties或 application.yaml中完成。既然这样,对于如何实现Zuul 的动态路由,我们很自然地会将它与Spring Cloud Config 的动态刷新机制联系到一起。只需将API 网关服务的配置文件通过Spring Cloud Config连接的Git仓库存储和管理,我们就能轻松实现动态刷新路由规则的功能。

在介绍如何具体实现.API网关服务的动态路由之前,我们首先需要一个连接到Git仓库的分布式配置中心config-server应用。如果还没有搭建过分布式配置中心的话,建议先阅读第8章的内容《springCloud08—分布式配置中心:Spring Cloud Config》,对分布式配置中心的运作机制有一个基础的了解,并构建一个config-server应用,以配合完成下面的内容。

在具备了分布式配置中心之后,为了方便理解我们重新构建一个API网关服务,该服务的配置中心不再配置于本地工程中,而是从config-server中获取,构建过程如下所示。

  • 创建一个基础的 Spring Boot web工程,命名为api-gateway-dynamic-route。

  • 在pom. xml中引入对zuul、eureka和config 的依赖,具体内容如下:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns=“http://maven.apache.org/POM/4.0.0” xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”

xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd”>

4.0.0

org.springframework.boot

spring-boot-starter-parent

2.4.8

com.cloud

api-gateway-dynamic-route

0.0.1-SNAPSHOT

api-gateway-dynamic-route

api-gateway-dynamic-route

<java.version>11</java.version>

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-test

test

org.springframework.cloud

spring-cloud-starter-netflix-eureka-client

org.springframework.cloud

spring-cloud-starter-netflix-zuul

2.2.9.RELEASE

org.springframework.cloud

spring-cloud-starter-config

org.springframework.cloud

spring-cloud-starter-bootstrap

org.springframework.cloud

spring-cloud-dependencies

2020.0.2

pom

import

创建bootstrap.properties文件

spring.application.name=api-getway

注册服务的时候使用服务的ip地址

eureka.instance.prefer-ip-address=true

eureka.client.service-url.defaultZone=http://localhost:1112/eureka/

开启通过服务来访问config server功能

spring.cloud.config.discovery.enabled=true

通过serviceId来指定config server注册的服务名

spring.cloud.config.discovery.service-id=config-server

#指定的环境

spring.cloud.config.profile=default

#指定分支,当使用git的时候,默认是master

spring.cloud.config.label=master

配置项目启动端口号

server.port=5556

设置安全访问的用户名和密码

spring.cloud.config.username=root

spring.cloud.config.password=123456

设置连接失败的快速响应

spring.cloud.config.fail-fast=true

#actuator配置

management.endpoints.enabled-by-default=true

management.endpoint.health.show-details=always

management.endpoints.web.exposure.include=*

management.endpoints.web.base-path=/actuator

同时在你的git仓库里放入api-getway.properties配置文件

在这里插入图片描述

文件配置如下:

路由配置

配置hello-service的服务路由

zuul.routes.api-a.path=/api-a/**

zuul.routes.api-a.serviceId=hello-service

feign-consumer的服务路由

zuul.routes.api-b.path=/api-b/**

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
true

management.endpoint.health.show-details=always

management.endpoints.web.exposure.include=*

management.endpoints.web.base-path=/actuator

同时在你的git仓库里放入api-getway.properties配置文件

在这里插入图片描述

文件配置如下:

路由配置

配置hello-service的服务路由

zuul.routes.api-a.path=/api-a/**

zuul.routes.api-a.serviceId=hello-service

feign-consumer的服务路由

zuul.routes.api-b.path=/api-b/**

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-qWYntNEC-1715080449121)]

[外链图片转存中…(img-s9InOGt0-1715080449122)]

[外链图片转存中…(img-YeuUJsip-1715080449122)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值