框架中的自定义网关----手撸一个网关

简介: spring cloud 中 gateway 提供了网关实现,高度自定义网关也可以自己实现一个

框架中的自定义网关

背景:

​ 有些公司项目中使用了自建的项目框架,网关进行了一些自定义的实现。如何实现一个自定义网关,又有哪些作用呢?本文将进行一些浅显的说明。

搭建一个项目 hand-gateway,并注册到nacos注册中心

项目依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

配置路由转发拦截器 RouteInterceptor

​ 路由映射可以从配置文件、配置中心或者数据库中进行加载,然后做映射转换,然后通过RestTemplate进行转发,restTemplate通过loadblance可以实现负载均衡。下面代码实现了Post、get的方法请求,json请求暂未实现,感兴趣的可以试下。

@Slf4j
@Component
public class RouteInterceptor implements HandlerInterceptor {

  @Autowired
  private RestTemplate restTemplate;
  @Autowired
  private DiscoveryClient discoveryClient;

  @Override
  public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    HttpHeaders httpHeaders = new HttpHeaders();
    String requestUrl = request.getRequestURI();
    if(StrUtil.isNotEmpty(request.getQueryString())){
      String query = URLDecoder.decode(request.getQueryString(),"UTF-8");
      requestUrl =requestUrl+"?"+query;
    }
    log.info("requestUrl {}",requestUrl);
    // 注册中心获取注册的服务
//    List<ServiceInstance> instances = discoveryClient.getInstances("paw-dogs-sky-service");
//    String ipAddr = instances.get(0).getUri().toString();
    // 加载路由映射 如配置文件、配置中心、redis、数据库
    Map<String,String> routerMap = new HashMap<>(16);
    routerMap.put("/sky-api","paw-dogs-sky-service");
    String routerKey = requestUrl;
    if(requestUrl.indexOf("/",1)>0){
      routerKey = requestUrl.substring(0,requestUrl.indexOf("/",1));
    }
    if(routerMap.containsKey(routerKey)){
      String serverName = routerMap.get(routerKey);
      // 做地址映射
      String targetUrl  = "http://"+requestUrl.replaceFirst(routerKey,serverName);

      log.info("requestUrl {} ==> targetUrl {}",requestUrl, targetUrl);

      ResponseEntity<String> responseEntity;
      if("POST".equalsIgnoreCase(request.getMethod())){
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        Map<String, String[]> paramMap = request.getParameterMap();
        for (String paramName: paramMap.keySet()) {
          params.addAll(paramName, Arrays.asList(paramMap.get(paramName)));
        }
        httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        HttpEntity<Object> requestEntity = new HttpEntity<>(params, httpHeaders);
        responseEntity = restTemplate.postForEntity(targetUrl, requestEntity, String.class);
      }else{
        responseEntity = restTemplate.getForEntity(targetUrl, String.class);
      }

      try {
        response.setStatus(responseEntity.getStatusCodeValue());
        String responseBody = responseEntity.getBody();
        response.setContentType("application/json;charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.print(responseBody);
        out.flush();
        out.close();
      } catch (IOException e) {
        e.printStackTrace();
      }

      return false;
    }
    return true;
  }

  @Override
  public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
  }

  @Override
  public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
  }
}

​ 增加权限校验拦截器 TokenInterceptor,获取token后根据验证方式 jwt 或者redis的方式进行权限校验,并将用户的一些信息放入到header中,往下传播。

@Slf4j
@Component
public class TokenInterceptor implements HandlerInterceptor {

  public static final String TOKEN_NAME = "token";

  @Override
  public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    log.info("TokenInterceptor requestUrl "+request.getRequestURI());
    String token = request.getHeader(TOKEN_NAME);
    if(StrUtil.isEmpty(token)){
      token = request.getParameter(TOKEN_NAME);
    }
    // 校验token jwt 或者 redis 此处省略
    if(StrUtil.isNotEmpty(token)){
      return true;
    }

    try {
      response.setStatus(HttpStatus.UNAUTHORIZED.value());
      response.setContentType("application/json;charset=UTF-8");
      PrintWriter out = response.getWriter();
      out.print(HttpStatus.UNAUTHORIZED.getReasonPhrase());
      out.flush();
      out.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
    return false;
  }
}

配置服务,RestTemplate添加@LoadBalanced注解进行负载均衡,添加拦截器,注意添加的顺序。

@Configuration
public class WebConfiguration implements WebMvcConfigurer {

  @Autowired
  private TokenInterceptor tokenInterceptor;

  @Autowired
  private RouteInterceptor routeInterceptor;

  @Bean
  @LoadBalanced
  @ConditionalOnClass(RestTemplate.class)
  public RestTemplate restTemplate(){
    return new RestTemplate();
  }

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(tokenInterceptor).addPathPatterns("/**").excludePathPatterns("/anonymous/**");
    registry.addInterceptor(routeInterceptor).addPathPatterns("/**");

  }

至此简单的网关项目完成。

搭建应用服务 sky-service,并注册到nacos注册中心

服务名称

spring:
  application:
    name: paw-dogs-sky-service

服务接口,增加port查看负载均衡,示例实现了get post方法

@RestController
public class SkyController {

  @Value("${spring.application.name:sky-service}")
  private String applicationName;

  @Value("${server.port:8080}")
  private Integer serverPort;

  @GetMapping("/sky")
  public String sky (@RequestParam(required = false) String name) {
    String msg = "this is " + applicationName + " port " + serverPort + " Time: " + DateUtil.now();
    if (StrUtil.isNotEmpty(name)) {
      msg = msg + " name: " + name;
    }
    return msg;
  }

  @PostMapping("/deliver")
  public String deliver (String packageBox) {
    String msg = "this is " + applicationName + " port " + serverPort + " Time: " + DateUtil.now();
    return msg + " delivered packageBox: " + packageBox;
  }

}

通过idea的方式设置不同的端口启动两个服务,VM options 设置 -Dserver.port=8081

通过postman访问网关项目,header 携带token

http://127.0.0.1:8080/sky-api/sky?name=fly

返回结果

this is paw-dogs-sky-service port 8082 Time: 2021-06-17 16: 45: 05 name: fly

访问Post请求,header携带token,form-urlencoded参数 packageBox=cake

http://127.0.0.1:8080/sky-api/deliver

返回结果

this is paw-dogs-sky-service port 8082 Time: 2021-06-17 16: 11: 23 delivered packageBox: cake

多访问几次会发现 port端口在8081 8082切换,负载均衡实现。

至此一个自定义网关项目雏形完成,可以根据项目实际需要自定义其他内容,增加拦截器来实现不同的业务,如增加签名校验,对路由映射也可以做更复杂的业务,如接口是否需要签名校验,再如增加业务统一的内容放到header中,增加接口限流等。

总结:

自定义网关是一个spring-boot-web项目,基于RestTemplate进行路由转发,通过LoadBlance实现负载均衡,通过拦截器实现路由映射、token权限校验及其他业务内容。

相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
EMQ
|
Prometheus 监控 网络协议
EMQX 5.0 全新网关框架:轻松实现多物联网协议接入
本文将对EMQX全新的网关框架及功能使用进行详细解读,帮助读者更好地利用EMQX的多协议接入能力连接各类设备,满足更多物联网场景的数据接入需求。
EMQ
697 1
EMQX 5.0 全新网关框架:轻松实现多物联网协议接入
|
6月前
|
Dubbo Java 应用服务中间件
Dubbo第二讲:深入理解dubbo分布式服务框架/负载/容错/调优/高可用/dubbo网关/面试/技术选型
Dubbo第二讲:深入理解dubbo分布式服务框架/负载/容错/调优/高可用/dubbo网关/面试/技术选型
150 0
|
Docker 容器
Docker | 自定义网络(网关、子网地址)
Docker | 自定义网络(网关、子网地址)
797 0
Docker | 自定义网络(网关、子网地址)
SpringCloud学习(十七):Gateway网关的自定义全局GlobalFilter
虽然官方为Gateway提供了很多filter,但其实并不使用,我们更多的还是使用自己的配置。 在9527网关模块中新建一个filter包,在里面写一个类来实现自定义filter
161 0
SpringCloud学习(十七):Gateway网关的自定义全局GlobalFilter
|
XML 负载均衡 监控
基于网关服务治理的研究与实践(三)微服务治理框架Spring Cloud
SpringCloud是Spring官方推出的微服务治理框架,是一个基于Spring Boot框架实现的微服务架构开发工具集,其提供了完整的微服务解决方案,包括:服务治理、注册中心、配置管理、熔断器、服务路由等等。
1266 0
基于网关服务治理的研究与实践(三)微服务治理框架Spring Cloud
云云对接中网关和子设备的自定义配置管理
调用云云对接的自定义管理配置。
95 0
|
Java
(四)Gateway开发教程之自定义网关过滤器
Gateway中一共提供了两种过滤器,一种是GatewayFilter、GlobalFilter; GatewayFilter:Gateway网关过滤器,是针对单个路由的过滤器,又称局部过滤器。GlobalFilter:从名称而言,那就是全局过滤器,
519 1
|
Serverless API PHP
PHPpraffa也有了,一个PHP版本的阿里云函数计算与API网关的开发框架
发布了Python版本的函数计算与API网关的开发框架后,一直觉得对不起PHP,因为公司一直是用PHP的,我这弄了个Python,实在不该,对了,(Python版本说明点这里。 PHPpraffa是什么? PHPpraffa 是praffa的PHP版本。
1346 0
|
Kubernetes 应用服务中间件 容器
Istio流量管理实践之(5): 使用cert-manager部署Istio自定义入口网关及进行证书管理
Istio Gateway提供多个自定义入口网关的支持能力,通过开放一系列端口用于承载网格边缘的进入连接,同时可以使用不同loadbalancer来隔离不同的入口流量。cert-manager可用于使用存储在Kubernetes Secret资源中的任意签名密钥对来获取证书。
3204 0
|
SQL 缓存 中间件
【.NET Core项目实战-统一认证平台】第七章 网关篇-自定义客户端限流
原文:【.NET Core项目实战-统一认证平台】第七章 网关篇-自定义客户端限流 【.NET Core项目实战-统一认证平台】开篇及目录索引 上篇文章我介绍了如何在网关上增加自定义客户端授权功能,从设计到编码实现,一步一步详细讲解,相信大家也掌握了自定义中间件的开发技巧了,本篇我们将介绍如何实现自定义客户端的限流功能,来进一步完善网关的基础功能。
1155 0