說到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請求的轉發,也方便加入自己的業務邏輯。