API 网关是一个更为智能的应用服务器,它的定义类似面向对象设计模式中的Facade模式,它的存在就像是整个微服务架构系统的门面一样,所有的外部客户端访问都需要经过它来进行调度和过滤。它除了要实现请求路由,负载均衡,校验过滤等功能之外,还需要更多能力,比如与服务治理框架的结合,请求转发时的熔断机制,服务的聚合等一系列高级功能

 

 

路由规则:

zuul默认会将通过以服务名作为contextPath的方式来创建路由映射,大部分情况下,这样的默认设置已经可以实现我们大部分的路由需求,如果有一些特殊的情况,还可以做一些特别的配置

在spring cloud zuul实现路由功能非常简单,只需要对网关服务增加一些关于路由规则的配置,就能实现传统的路由转发功能,比如

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

zuul.routes.api-a-url.url=http://localhost:8080

其中api-a-url 为路由的名字,可以任意定义

上面配置所表达的意思是

所有符合/api-a-url/**规则的访问都被路由转发到http://localhost:8080/地址上,比如当我们访问

http://{zuul.hostname}:{zuul.port}/api-a-url/hello的时候,API网关服务会将请求路由到http://localhost:8080/hello

提供的微服务接口上

 

面向服务的路由

spring cloud zuul实现了与 spring cloud eureka的无缝整合,我们可以让路由的path不是映射具体的url,而是让它

映射到某个具体的服务,而具体的url则交给eureka的服务发现机制去自动维护

spring.application.name=api-gateway

server.port=5555

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

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

 

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

zuul.routes.api-b.serviceId=feign-consumer

 

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

 

 

签名校验:

开发者可以通过使用zuul来创建各种校验器,然后指定哪些规则的请求需要执行校验逻辑,只有通过校验的才会被路由到具体的微服务接口,不然就返回错误提示

每个客户端用户请求微服务应用提供的接口时,它们的访问权限往往都有一定的限制。在不使用网关的时候,我们可能会对每个服务消费者定义过滤器或拦截器等拦截每个请求,若每个微服务都实现一套鉴权的逻辑,而且每个微服务鉴权的逻辑差不多,这样会带来代码的冗余。

在引入网关层后,我们可以在网关完成校验和过滤,这使得微服务应用接口的开发和测试复杂度也得到相应降低

 

服务网关 Zuul基本使用

 

版本 spring boot 1.5.14

 

eureka注册中心

配置文件

server.port=1111

eureka.instance.hostname=localhost

#由于该应用为注册中心,所有设置为false,代表不向注册中心注册自己

eureka.client.register-with-eureka=false

#由于注册中心的职责就是维护服务实例,它并不需要去检索服务,所以也设置为false

eureka.client.fetch-registry=false

eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/

 

启动类

package springcloud.eurekaserver;

 

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

 

@EnableEurekaServer // 注解启动一个服务注册中心

@SpringBootApplication

public class EurekaServerApplication {

public static void main(String[] args) {

SpringApplication.run(EurekaServerApplication.class, args);

}

}

 

服务提供者

配置文件

spring.application.name=hello-service

eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

 

controller

package springcloud.eureka_provider_pt;

 

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.cloud.client.discovery.DiscoveryClient;

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

import org.springframework.web.bind.annotation.RequestMethod;

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

 

@RestController

public class HelloController {

@Autowired

private DiscoveryClient client;

@Qualifier("eurekaRegistration")

@Autowired

private org.springframework.cloud.client.serviceregistry.Registration eurekaRegistration;

 

@RequestMapping(value = "/hello",method = RequestMethod.GET)

public String index(){

System.out.println("serviceId:"+eurekaRegistration.getServiceId()+" hostName:"+eurekaRegistration.getHost());

return "Hello World eueka_provider gz1";

}

}

 

启动类

package springcloud.eureka_provider_pt;

 

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

 

@EnableDiscoveryClient

@SpringBootApplication

public class EurekaProviderPtApplication {

public static void main(String[] args) {

SpringApplication.run(EurekaProviderPtApplication.class, args);

}

}

 

 

服务消费者

 

配置文件

spring.application.name=ribbon-consumer

server.port=9000

eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

 

controller

package springcloud.eureka_consumer_pt;

 

import org.springframework.beans.factory.annotation.Autowired;

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

import org.springframework.web.bind.annotation.RequestMethod;

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

import org.springframework.web.client.RestTemplate;

 

@RestController

public class ConsumerController {

@Autowired

RestTemplate restTemplate;

 

 

@RequestMapping(value = "/ribbon-consumer",method = RequestMethod.GET)

public String helloConsumer(){

 

 

return restTemplate.getForEntity("http://HELLO-SERVICE/hello",String.class).getBody();

}

 

}

 

启动类

package springcloud.eureka_consumer_pt;

 

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;

import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

import org.springframework.context.annotation.Bean;

import org.springframework.web.client.RestTemplate;

 

@EnableDiscoveryClient

@SpringBootApplication

public class EurekaConsumerPtApplication {

 

public static void main(String[] args) {

SpringApplication.run(EurekaConsumerPtApplication.class, args);

}

 

@Bean

@LoadBalanced

RestTemplate restTemplate(){

return new RestTemplate();

}

 

}

 

 

zuul网关

 

配置文件

spring.application.name=api-gateway

server.port=5555

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

zuul.routes.api-b.serviceId=ribbon-consumer

 

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

filter 类

package springcloud.zuul;

 

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 javax.servlet.http.HttpServletRequest;

 

public class AccessFilter extends ZuulFilter{

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

/*

过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。

这里定义为 pre,代表会在请求被路由之前执行

routing: 在路由请求时被调用

post: 在routing 和 error 过滤器之后被调用

error: 处理请求时发生错误时被调用

*/

@Override

public String filterType() {

return "pre";

}

 

/*

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

*/

@Override

public int filterOrder() {

return 0;

}

 

/*

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

来指定过滤器的有效范围

*/

@Override

public boolean shouldFilter() {

return true;

}

 

/*

过滤器的具体逻辑。这里我们通过 ctx.setSendZuulResponse(false);令zuul 过滤该请求,不对其进行路由

,然后ctx.setResponseStatusCode(401);设置了返回错误码,

*/

@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.getParameter("accessToken");

if(accessToken==null){

log.warn("access token is empty");

ctx.setSendZuulResponse(false);

ctx.setResponseStatusCode(401);

return null;

}

log.info("access token ok");

return null;

}

}

启动类

package springcloud.zuul;

 

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.client.SpringCloudApplication;

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

import org.springframework.context.annotation.Bean;

 

@EnableZuulProxy

@SpringCloudApplication

public class ZuulApplication {

 

public static void main(String[] args) {

SpringApplication.run(ZuulApplication.class, args);

}

 

@Bean

public AccessFilter accessFilter(){

return new AccessFilter();

}

 

}

 

结果:

 

 

参考  spring cloud 微服务实战<程序员DD>

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐