当前位置: 首页 > backend >正文

SpringBoot实现简单的API代理服务器

背景

SpringBoot+K8S应用,没有SpringCloud, 想实现一个类似SpringCloudGateway的一个东西,可以把前端的请求路由到不同的后端服务上去,后端服务可能有多个,比如用户服务、订单服务,每一个服务都有自己的单独的访问域名。

思路

提供一个API代理服务,前端访问的时候,添加统一的前缀比如/api进入代理服务的ApiController,ApiController需要识别出来这个请求要访问的是哪一个后端服务,在SpringCloud中我们可以借助Nacos来做,现在只能手动做配置,把服务名追加到url中。
当ApiController收到请求的时候,首先从url中解析出来服务名,然后根据服务名查询配置文件找到实际的后端要访问的服务地址,拼接上url中剩余部分作为实际要转发的url,转发请求,返回响应。
举个例子:
前端实际要访问的后端地址是:http://www.userservice.com/use/1
前端发起的请求地址需要改成:http://www.api-proxy.com/api/userservice/use/1
在API代理服务中添加配置:

api:service:userservice: http://www.userservice.comorderservice: http://www.orderservice.com

这里url中的/api用于定位到后端的ApiController, userservice用于定位实际要访问的后端服务。
当API代理服务收到http://www.api-proxy.com/api/userservice/use/1请求的时候,首先从url中解析出userservice,根据配置得到前缀是http://www.userservice.com,然后拼接剩余部分/use/1得到真正要访问的url是http://www.userservice.com/use/1
当然,如果可以配置多个后端服务地址然后可以实现自己的负载均衡,这样就更像一个网关了。

核心代码

@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping(ApiController.API_CONTROLLER_PREFIX)
public class ApiController implements UserDetailAware {public static final String API_CONTROLLER_PREFIX = "/api";private static final String HEADER_USER_ID = "";private final RestTemplate restTemplate;private final ProxyConfig proxyConfig;@RequestMapping(value = "/{serviceName}/**")public ResponseEntity proxy(@PathVariable("serviceName") String serviceName, HttpServletRequest request, HttpServletResponse response) throws Exception{try {String serviceUrlPrefix = proxyConfig.getServices().get(serviceName);if (StringUtils.isEmpty(serviceUrlPrefix)) {return new ResponseEntity("No API Service Found!", HttpStatus.NOT_FOUND);}String url = buildUlr(serviceUrlPrefix, request);RequestEntity requestEntity = buildRequestEntity(url, request, response);ResponseEntity<byte[]> responseEntity = restTemplate.exchange(requestEntity, byte[].class);return new ResponseEntity(responseEntity.getBody(), responseEntity.getStatusCode());} catch (Exception e) {log.error(ExceptionUtils.getStackTrace(e));return buildErrorResponseEntity(e);}}private ResponseEntity buildErrorResponseEntity(Exception e) throws Exception{if (e instanceof RestClientResponseException exception) {String errorBody = "Internal Server Error";byte[] errorBytes = exception.getResponseBodyAsByteArray();if (!ObjectUtils.isEmpty(errorBytes)) {errorBody = new String(errorBytes, StandardCharsets.UTF_8);}return new ResponseEntity(errorBody, exception.getStatusCode());} else {throw e;}}private RequestEntity buildRequestEntity(String url, HttpServletRequest request, HttpServletResponse response) throws IOException {HttpHeaders headers = buildHeader(request, response);if (isMultipart(request)) {return getMultipartEntity(url, request, headers);} else {byte[] body = StreamUtils.copyToByteArray(request.getInputStream());return new RequestEntity(body, headers, HttpMethod.valueOf(request.getMethod()), URI.create(url));}}private RequestEntity getMultipartEntity(String url, HttpServletRequest request, HttpHeaders headers) throws IOException {MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request;MultiValueMap<String, MultipartFile> multiFileMap = multipartHttpServletRequest.getMultiFileMap();// filesMultiValueMap formData = new LinkedMultiValueMap();for (Map.Entry<String, List<MultipartFile>> entry : multiFileMap.entrySet()) {String key = entry.getKey();for (MultipartFile multipartFile : entry.getValue()) {ByteArrayResource fileResource = new ByteArrayResource(multipartFile.getBytes()) {@Overridepublic long contentLength() {return multipartFile.getSize();}@Overridepublic String getFilename() {return multipartFile.getOriginalFilename();}};formData.add(key, fileResource);}}//non-filesMap<String, String[]> parameterMap = multipartHttpServletRequest.getParameterMap();for (Map.Entry<String, String[]> stringEntry : parameterMap.entrySet()) {String key = stringEntry.getKey();for (String value : stringEntry.getValue()) {formData.add(key, value);}}headers.setContentType(MediaType.MULTIPART_FORM_DATA);return new RequestEntity(formData, headers, HttpMethod.valueOf(request.getMethod()), URI.create(url));}private boolean isMultipart(HttpServletRequest request) {return request instanceof MultipartHttpServletRequest;}private HttpHeaders buildHeader(HttpServletRequest request, HttpServletResponse response) {HttpHeaders headers = new HttpHeaders();UserDetailsImpl userDetails = getUserDetails();Integer userId= userDetails==null?null:userDetails.getUserId();headers.add(HEADER_USER_ID , String.valueOf(userId));Iterator<String> iterator = request.getHeaderNames().asIterator();while (iterator.hasNext()) {String headerName = iterator.next();List<String> headerValues = Collections.list(request.getHeaders(headerName));for (String headerValue : headerValues) {headers.add(headerName, headerValue);}}return headers;}private String buildUlr(String serviceUrlPrefix, HttpServletRequest request) {String srcURI = request.getRequestURI();String dstURI = srcURI.substring(srcURI.indexOf(API_CONTROLLER_PREFIX) + API_CONTROLLER_PREFIX.length());dstURI = dstURI.substring(dstURI.indexOf("/", 1));return UriComponentsBuilder.fromUriString(serviceUrlPrefix).path(dstURI).query(request.getQueryString()).build().toUriString();}
}

参考文档

  • https://blog.csdn.net/u013887008/article/details/128060629
http://www.xdnf.cn/news/6455.html

相关文章:

  • Sumsub 活体检测与人证对比 Java Demo
  • pytorch训练可视化工具---TensorBoard
  • Linux 防火墙 firewalld 实战配置教程!
  • 将.pt文件执行图像比对
  • Java详解RabbitMQ工作模式之发布订阅模式
  • 具备AI功能的银河麒麟桌面操作系统已正式上市
  • 手搓传染病模型(SEI - SEIAR )
  • xp_cmdshell bcp 导出文件
  • 道通龙鱼系列-混合翼无人机:垂直起降+长时续航
  • 嵌入式自学第二十二天(5.15)
  • 02、基础入门-Spring生态圈
  • 云上玩转 Qwen3 系列之三:PAI-LangStudio x Hologres构建ChatBI数据分析Agent应用
  • 机器学习第十三讲:独热编码 → 把“红黄蓝“颜色变成001/010/100的数字格式
  • 数据结构之图的应用场景及其代码
  • MySQL 用户权限管理:从入门到精通
  • 26考研 | 王道 | 计算机组成原理 | 一、计算机系统概述
  • Java:跨越时代的编程语言传奇
  • 2025年黑客扫段攻击激增:如何构建智能防御体系保障业务安全?
  • Makefile与CMake
  • AI大模型应用:17个实用场景解锁未来
  • 软件设计师考试《综合知识》CPU考点分析(2019-2023年)——求三连
  • 让AI帮我写一个word转pdf的工具
  • 从《西游记》到微调大模型:一场“幻觉”与“认知”的对话20250515
  • 在 VMware 中挂载 U 盘并格式化为 ext4 文件系统的完整指南
  • 企业在蓝海市场有哪些推进目标?
  • 操作系统学习笔记第3章 内存管理(灰灰题库)
  • 嵌入式学习--江科大51单片机day7
  • Metagloves Pro+Manus Core:一套组合拳打通虚拟制作与现实工业的任督二脉
  • 题海拾贝:P4017 最大食物链计数
  • 399. 除法求值