首页 > 编程学习 > boot-接口代理服务转发实现

boot-接口代理服务转发实现

发布时间:2022/11/27 10:56:22

boot-接口代理服务实现

背景

业务中对接了第三方服务,有些接口需要进行透传转发,又不想直接经过公网网关,所有就要实现一个类似网关转发的接口

目标

  • 实现基本的接口转发,get, post请求 (比较容易)
  • 支持文件转发请求

转发原理

  • 接收客户端所有的请求参数,从request中获取请求参数,如headers, queryParameters,文件信息
  • 使用客户端工具,如httpclient 或者使用 restTemplate 客户端进行参数再次拼接转发请求
  • 我们使用 restTemplate ,如果要要求性能,可以使用连接池

实现

RouteProperties, RouteInfo 路由信息


@Data
@Configuration
@ConfigurationProperties(prefix = "proxy.svc")
public class RouteProperties {
    //map<服务,路由>
    private Map<String, RouteInfo> routes;

    /**
     * 获取路由
     *
     * @param prefix
     * @return
     */
    public RouteInfo getRouteByPrefix(String prefix) {
        return routes.entrySet().stream()
                .map(Map.Entry::getValue).filter(r -> r.getPrefix().equals(prefix))
                .findFirst().orElse(null);
    }
}

@Data
public class RouteInfo {
    /**
     * 路由前缀
     */
    private String prefix;
    /**
     * 主机 http://开头
     */
    private String host;
}

ProxyController

@Slf4j
@RestController
public class ProxyController {

    @Autowired
    private RouteProperties routeProperties;

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 请求转发
     * 支持 文件转发
     *
     * @param prefix
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(value = "/{prefix}/**")
    public ResponseEntity proxy(@PathVariable String prefix, HttpServletRequest request, HttpServletResponse response) {
        try {
            RouteInfo route = routeProperties.getRouteByPrefix(prefix);
            if (route == null) {
                return new ResponseEntity("No route found!", HttpStatus.INTERNAL_SERVER_ERROR);
            }
            String url = rebuildUlr(request, route.getHost(), prefix);
            log.info("proxy url: {}", url);
            RequestEntity requestEntity = buildRequestEntity(url, request);
            return restTemplate.exchange(requestEntity, String.class);
        } catch (Exception e) {
            return new ResponseEntity("server Error", HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    private RequestEntity buildRequestEntity(String url, HttpServletRequest request) throws IOException {
        //获取 所有heads
        HttpHeaders headers = parseHeader(request);
        //单独处理文件上传
        if (isMultipart(request)) {
            RequestEntity formData = getFormDataEntity(url, request, headers);
            if (formData != null) {
                return formData;
            }
            throw new RuntimeException("参数异常");
        }
        //这里获取不到 form-data 中数据,只能获取,requestBody, form-urlencoded-www参数
        byte[] body = parseBody(request);
        log.info("request: {}", new String(body));
        return new RequestEntity(body, headers, HttpMethod.resolve(request.getMethod()), URI.create(url));
    }


    private RequestEntity getFormDataEntity(String url, HttpServletRequest request, HttpHeaders headers) throws IOException {
        MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request;
        MultiValueMap<String, MultipartFile> multiFileMap = multipartHttpServletRequest.getMultiFileMap();
        //处理文件
        MultiValueMap 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()) {
                    @Override
                    public long contentLength() {
                        return multipartFile.getSize();
                    }

                    @Override
                    public String getFilename() {
                        return multipartFile.getOriginalFilename();
                    }
                };
                formData.add(key, fileResource);
            }
        }
        //处理 form-data 中非 文件类型参数
        Map<String, String[]> parameterMap = multipartHttpServletRequest.getParameterMap();
        for (Map.Entry<String, String[]> stringEntry : parameterMap.entrySet()) {
            for (String s : stringEntry.getValue()) {
                formData.add(stringEntry.getKey(), s);
            }
        }
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        return new RequestEntity(formData, headers, HttpMethod.resolve(request.getMethod()), URI.create(url));
    }

    private boolean isMultipart(HttpServletRequest request) {
        return request instanceof MultipartHttpServletRequest;
    }

    private HttpHeaders parseHeader(HttpServletRequest request) {
        HttpHeaders headers = new HttpHeaders();
        Iterator<String> iterator = request.getHeaderNames().asIterator();
        while (iterator.hasNext()) {
            String next = iterator.next();
            List<String> list = Collections.list(request.getHeaders(next));
            for (String s : list) {
                headers.add(next, s);
            }
        }
        return headers;
    }

    private byte[] parseBody(HttpServletRequest request) throws IOException {
        return StreamUtils.copyToByteArray(request.getInputStream());
    }

    private String rebuildUlr(HttpServletRequest request, String host, String prefix) {
        String query = request.getQueryString();
        return host.concat(request.getRequestURI().replace(prefix, "")).concat(query != null ? "?".concat(query) : "");
    }
}

application.properties 配置文件

# 设置文件上传大小
spring.servlet.multipart.max-file-size=2GB
spring.servlet.multipart.max-request-size=2GB
# user 服务 的 前缀
proxy.svc.routes.user.prefix=usr
# user 服务的目标地址
proxy.svc.routes.user.host=http://localhost:9090

访问

http://localhost:8080/usr/user/1:

{
    "data": {
        "id": 1,
        "name": "test",
        "age": 12
    },
    "code": "1",
    "message": null
}

good luck!

Copyright © 2010-2022 dgrt.cn 版权所有 |关于我们| 联系方式