说到http转发,很容易想到用Nginx,因为Nginx性能好,配置简单。但如果希望能动态配置路由规则,或者在转发时增加自己的逻辑,使用Nginx就比较难实现。
本文将讲述如何使用springboot来搭建一个http转发服务器,实现http请求的动态转发。
实现http转发服务器,需要两个东西:
1)请求转发器,负责接收请求以及转发请求
2)转发规则
先说转发规则,转发规则可以用一张表(proxy_route)来储存,主要字段如下:
1)prefix,请求前缀
2)targetUrl,转发地址
3)description,规则说明
录好转发规则后,后面直接上转发器的代码
@RestController
@RequestMapping(ProxyController.PROXY_PREFIX)
public class ProxyController {
public final static String PROXY_PREFIX = "/proxy";
@Autowired
private ServletContext servletContext;
@Autowired
private IProxyService proxyService;
// 待转发的请求总入口
@RequestMapping(value = "/**")
public ResponseEntity<?> catchAll(HttpServletRequest request, HttpServletResponse response) {
// String tenantId = 从session中获取租户ID;
String prefix = "/" + servletContext.getContextPath() + "/" + PROXY_PREFIX;
prefix = prefix.replaceAll("/+", "/");
// 获取 /** 内容
String uri = request.getRequestURI();
String targetPath = uri.substring(prefix.length());
return proxyService.redirect(tenantId, request, response, targetPath);
}
}
@Service
public class ProxyServiceImpl implements IProxyService {
private RestTemplate restTemplate;
@PostConstruct
private void init() {
restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new ByteArrayHttpMessageConverter());
}
@Override
public ResponseEntity<?> redirect(String tenantId, HttpServletRequest request, HttpServletResponse response,
String targetPath) {
String redirectUrl = null;
try {
// build up the redirect URL
redirectUrl = createRedictUrl(tenantId, request, targetPath);
RequestEntity<?> requestEntity = createRequestEntity(request, redirectUrl);
return route(requestEntity);
} catch (ProxyRouteNotFoundException e) {
log.error("找不到符合的转发路由:" + targetPath, e);
return new ResponseEntity<String>("Proxy Route Not Found", HttpStatus.BAD_REQUEST);
} catch (Exception e) {
log.error("请求转发[" + targetPath + "]失败", e);
return new ResponseEntity<String>("REDIRECT ERROR: " + e.getMessage() + ", URL: " + redirectUrl,
HttpStatus.INTERNAL_SERVER_ERROR);
}
}
private String createRedictUrl(String tenantId, HttpServletRequest request, String targetPath) {
String queryString = request.getQueryString();
String targetUrl = getTargetUrl(tenantId, targetPath);
return targetUrl + (queryString != null ? "?" + queryString : "");
}
private String getTargetUrl(String tenantId, String targetPath) {
// 寻找匹配的转发规则
}
private RequestEntity<?> createRequestEntity(HttpServletRequest request, String url)
throws URISyntaxException, IOException {
String method = request.getMethod();
HttpMethod httpMethod = HttpMethod.resolve(method);
MultiValueMap<String, String> headers = parseRequestHeader(request);
Object body = parseRequestBody(request);
return new RequestEntity<>(body, headers, httpMethod, new URI(url));
}
private ResponseEntity<?> route(RequestEntity<?> requestEntity) {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<byte[]> response = restTemplate.exchange(requestEntity, byte[].class);
return response;
}
private Object parseRequestBody(HttpServletRequest request) throws IOException {
InputStream inputStream = request.getInputStream();
return StreamUtils.copyToByteArray(inputStream);
}
private MultiValueMap<String, String> parseRequestHeader(HttpServletRequest request) throws IOException {
HttpHeaders headers = new HttpHeaders();
List<String> headerNames = Collections.list(request.getHeaderNames());
// 保留旧的请求头
for (String headerName : headerNames) {
List<String> headerValues = Collections.list(request.getHeaders(headerName));
for (String headerValue : headerValues) {
headers.add(headerName, headerValue);
}
}
return headers;
}
}
如果有上传文件的请求转发需求,则需要修改下,其中MultipartFileResource类是spring 5.1以上才有,5.1以下的项目可以直接把这个类拷到自己项目中使用。
private Object parseRequestBody(HttpServletRequest request) throws IOException {
// 支持文件上传
if (request instanceof StandardMultipartHttpServletRequest) {
StandardMultipartHttpServletRequest fileReq = (StandardMultipartHttpServletRequest) request;
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
for (String key : fileReq.getFileMap().keySet()) {
parts.add(key, new MultipartFileResource(fileReq.getFile(key)));
}
return parts;
}
InputStream inputStream = request.getInputStream();
return StreamUtils.copyToByteArray(inputStream);
}
上述http请求转发器,虽然性能比不上Nginx,但可以动态实现http请求的转发,也方便加入自己的业务逻辑。