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!