写点什么

框架中的自定义网关

作者:Rubble
  • 2022 年 4 月 09 日
  • 本文字数:3663 字

    阅读完需:约 12 分钟

框架中的自定义网关

背景:

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


搭建一个项目 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@Componentpublic 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@Componentpublic 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 注解进行负载均衡,添加拦截器,注意添加的顺序。


@Configurationpublic 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 方法


@RestControllerpublic 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 权限校验及其他业务内容。

用户头像

Rubble

关注

还未添加个人签名 2021.06.01 加入

还未添加个人简介

评论

发布
暂无评论
框架中的自定义网关_4月日更_Rubble_InfoQ写作平台