spring cloud备忘笔记-6-API网关统一访问接口
- 引入
- Zuul
- 创建Zuul服务
- 测试
- 甩锅
- Zuul的过滤器
- 测试过滤器
笔记索引:
spring cloud备忘笔记-0-目录索引
引入
通常我们的客户端的的一个页面可能调用了多个服务,而我们让客户端直接记住所有的rest api,连那么多的服务是不太现实的选择。我们需要来一个网关,客户端来找api网关,由网关统一负责找所有的服务。
客户端
负载均衡服务器
Api网关1
Api网关2
Api网关3
各种服务1,2,3,4,5
Api网关和服务都需要注册到服务注册中心,最后的配置文件也要独立部署,将配置服务文件放在git仓库里。也就是项目中是不需要配置application.yml的而是在仓库中部署。这样我们只要动态的修改配置文件,直接上传到git仓库。所有的服务立刻就可以拿到最新的配置,就不用为了修改配置文件而重启服务了。这是配置的云部署
模式。
Zuul
这边我们是用Zuul来做服务网关 。它的主要功能是路由转发
和过滤器
。我们可以配置/api/a即转发到A服务,配置/api/b就转发到B服务。
创建Zuul服务
hi-spring-cloud-zuul
创建步骤见spring cloud备忘笔记-0-目录索引 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.momomian</groupId>
<artifactId>hi-spring-cloud-dependencies</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../hi-spring-cloud-dependencies/pom.xml</relativePath>
</parent>
<artifactId>hi-spring-cloud-zuul</artifactId>
<packaging>jar</packaging>
<name>hi-spring-cloud-zuul</name>
<inceptionYear>2019-Now</inceptionYear>
<dependencies>
<!-- Spring Boot Begin -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot End -->
<!-- Spring Cloud Begin -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!-- Spring Cloud End -->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.momomian.hi.spring.cloud.zuul.ZuulApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
配置文件入下,这里将服务的路由routes配置好,这里的api-a是自己取得。serviceId指定服务标识。这里feign得path配置的是: /api/b/**
而 ribbon的是/api/a/**
,我们之后调用的时候就得加上这个路径了。
application.yml
spring:
application:
name: hi-spring-cloud-zuul
server:
port: 8786
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8780/eureka/
zuul:
routes:
api-a:
path: /api/a/**
serviceId: hi-spring-cloud-web-admin-ribbon
api-b:
path: /api/b/**
serviceId: hi-spring-cloud-web-admin-feign
ZuulApplication .java
使用注解@EnableZuulProxy
package com.momomian.hi.spring.cloud.zuul;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class,args);
}
}
测试
依次运行 EurekaApplication、ServiceAdminApplication、WebAdminRibbonApplication、WebAdminFeignApplication、ZuulApplication,
此时之前调用接口得方式就得改了,我们调用feign得服务接口hi:http://localhost:8785/api/b/hi?message=hi 这边我们已经部署了五个服务了,所以电脑性能注意下,这边如果出现500,timed out 超时,可以多刷几次。但是这个失败是由于网络原因导致的。我们现在无法确保高可用,就要做好回调处理。
甩锅
我们可以创建一个类实现它提供的一个回调接口:import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
这里面需要实现两个方法:getRoute和fallbackResponse
前者我们返回需要回调的服务name即可,
后者是用于请求服务失败,就会返回指定的信息给调用者的方法,我们拿ClientHttpResponse对象实现。
拿feign这个服务试一下,
package com.momomian.hi.spring.cloud.zuul.fallback;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
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.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
@Component
public class WebAdminFeignFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
// ServiceId,如果需要所有调用都支持回退,则 return "*" 或 return null
return "hi-spring-cloud-web-admin-feign";
}
/**
* 如果请求服务失败,则返回指定的信息给调用者
* @param route
* @param cause
* @return
*/
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.OK.value();
}
@Override
public String getStatusText() throws IOException {
return HttpStatus.OK.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> map = new HashMap<>();
map.put("status", 200);
map.put("message", "无法连接,请检查您的网络");
return new ByteArrayInputStream(objectMapper.writeValueAsString(map).getBytes("UTF-8"));
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
// 响应json数据,和 getBody 中的内容编码一致
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
return headers;
}
};
}
}
这里我们网关向 api 服务请求失败了,但是消费者客户端向网关发起的请求是成功的,
注意:我们不应该把 api 的 404,500 等问题抛给客户端,网关和 api 服务集群对于客户端来说应该是黑盒的。
所以我们要把状态码改成200,提示用户是否有网络问题
这里其实是自己的问题,但是我们通常要把问题赖给用户,我们称之为甩锅
。
Zuul的过滤器
之前我们使用spingmvc实现了过滤器,现在我们可以使用spring cloud使用zuul来创建过滤器,做安全验证的功能。
我们写个类继承ZuulFilter,之后实现四个方法,其中主要是run()方法来实现具体业务功能,
filterType方法指定过滤类型,如下:
pre:路由之前
routing:路由之时
post:路由之后
error:发送错误调用
shouldFilter方法用于配置是否要过滤。filterOrder是指定过滤顺序,数值越小越优先。
接下来我们看具体实现过滤:逻辑上就是我们要拿到某个请求,然后检测有没有带我需要的某个参数。没有带我就拦截你。
这里我们想要取到HttpRequest,因为使用的是zuul,那就直接看看它老人家有没有。我们看这个类com.netflix.zuul.context.RequestContext;
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
这样就拿到了,我们只要判断里面有没有我们需要的参数比如:token,根据自己的规则判断。如下:
package com.momomian.hi.spring.cloud.zuul.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Component
public class LoginFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(LoginFilter.class);
//在路由之前调用
@Override
public String filterType() {
return "pre";
}
/**
* 配置过滤的顺序
* @return
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 配置是否需要过滤:true/需要,false/不需要
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 过滤器的具体业务代码
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
logger.info("{} >>> {}", request.getMethod(), request.getRequestURL().toString());
String token = request.getParameter("token");
if (token == null) {
logger.warn("Token is empty");
//不发送zuul响应了
context.setSendZuulResponse(false);
//没有权限不允许访问
context.setResponseStatusCode(401);
try {
//然后写点东西
//指定编码
HttpServletResponse response = context.getResponse();
response.setContentType("text/html;charset=utf-8");
context.getResponse().getWriter().write("不允许请求");
} catch (IOException e) {
}
} else {
logger.info("OK");
}
return null;
}
}
实际的token的规则按照具体需求规则分配,这里只是简单判断有没有。
测试过滤器
以下:浏览器访问:http://localhost:8785/api/b/hi?message=HelloZuul&token=123 网页显示正常,不带网页显示我们配置的过滤器response。
笔记索引:
spring cloud备忘笔记-0-目录索引