Springboot版动态代理网关实现
背景首先使用代理,希望接口结果直接时代理方的,不希望有封装;其次使用一些云原生网关时,业务有时会希望干预一下代理流程,比如请求前做点通知、事后存点日志,这些云原生网关不好处理,还是得代码处理;所以引出下文。servlet方式(smiley-http-proxy-servlet)特点基于Servlet进行服务代理,只需要进行相关的配置之后,就能进行服务代理有特殊要求要对代理服务进行改造,如下文需要进
·
背景
首先使用代理,希望接口结果直接是代理方的,不希望有封装;
其次使用一些云原生网关时,业务有时会希望干预一下代理流程,比如请求前做点通知、事后存点日志,这些云原生网关不好处理,还是得代码处理;
所以引出下文。
servlet方式(smiley-http-proxy-servlet)
特点
- 基于Servlet进行服务代理,只需要进行相关的配置之后,就能进行服务代理
- 有特殊要求要对代理服务进行改造,如下文需要进行动态的路由代理,增删路由不需要修改代码/重启服务
Spring Boot示例
这里以动态网关进行示例
Maven文件
<!-- https://mvnrepository.com/artifact/org.mitre.dsmiley.httpproxy/smiley-http-proxy-servlet -->
<dependency>
<groupId>org.mitre.dsmiley.httpproxy</groupId>
<artifactId>smiley-http-proxy-servlet</artifactId>
<version>1.11</version>
</dependency>
自定义扩展(核心)
- init方法:注入logbean及redistemplate
- service方法:用于根据路径判断不同路由进入不同地址中
- doExecute方法:进行pre操作或after操作
public class MyProxyServlet extends ProxyServlet {
/**
* redis连接实例
*/
private ValueOperations valueOperations = null;
private LogDao sysQueryLogDao = null;
/**
* 临时定义代理地址
*/
private final Map<String, String> urlMap = new HashMap<String, String>(){
{
put("hao123", "http://www.hao123.com");
put("baidu", "http://www.baidu.com");
}
};
@Override
public void init() throws ServletException {
super.init();
sysQueryLogDao = SpringContextUtils.getBean("sysQueryLogDao", LogDao.class);
valueOperations = SpringContextUtils.getBean("valueOperations", ValueOperations.class);
}
@Override
protected void service(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws ServletException, IOException {
// TODO 后续可更改成从redis/数据库取代理路径,实现动态反向代理
// 初始切换路径
String path = servletRequest.getPathInfo();
String firstPath = getFirstPath(path);
String url = urlMap.get(firstPath);
if (StringUtils.isEmpty(url)) {
throw new ServletException("代理路径不正确,请找管理员确认路径");
}
if (servletRequest.getAttribute(ATTR_TARGET_URI) == null) {
servletRequest.setAttribute(ATTR_TARGET_URI, url);
}
if (servletRequest.getAttribute(ATTR_TARGET_HOST) == null) {
URL trueUrl = URLUtil.url(url);
servletRequest.setAttribute(ATTR_TARGET_HOST, new HttpHost(trueUrl.getHost(), trueUrl.getPort(), trueUrl.getProtocol()));
}
String method = servletRequest.getMethod();
// 替换多余路径
String proxyRequestUri = this.rewriteUrlFromRequest(servletRequest);
proxyRequestUri = proxyRequestUri.replaceFirst("/"+firstPath, "");
Object proxyRequest;
if (servletRequest.getHeader("Content-Length") == null && servletRequest.getHeader("Transfer-Encoding") == null) {
proxyRequest = new BasicHttpRequest(method, proxyRequestUri);
} else {
proxyRequest = this.newProxyRequestWithEntity(method, proxyRequestUri, servletRequest);
}
this.copyRequestHeaders(servletRequest, (HttpRequest)proxyRequest);
setXForwardedForHeader(servletRequest, (HttpRequest)proxyRequest);
HttpResponse proxyResponse = null;
try {
proxyResponse = this.doExecute(servletRequest, servletResponse, (HttpRequest)proxyRequest);
int statusCode = proxyResponse.getStatusLine().getStatusCode();
servletResponse.setStatus(statusCode, proxyResponse.getStatusLine().getReasonPhrase());
this.copyResponseHeaders(proxyResponse, servletRequest, servletResponse);
if (statusCode == 304) {
servletResponse.setIntHeader("Content-Length", 0);
} else {
this.copyResponseEntity(proxyResponse, servletResponse, (HttpRequest)proxyRequest, servletRequest);
}
} catch (Exception var11) {
this.handleRequestException((HttpRequest)proxyRequest, var11);
} finally {
if (proxyResponse != null) {
EntityUtils.consumeQuietly(proxyResponse.getEntity());
}
}
}
@Override
protected HttpResponse doExecute(HttpServletRequest servletRequest, HttpServletResponse servletResponse, HttpRequest proxyRequest) throws IOException {
HttpResponse response = null;
// 拦截校验
String token = servletRequest.getHeader("ex_proxy_token");
if (StringUtils.isEmpty(token)) {
// TODO 检验token正确性
response = new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, ""));
BasicHttpEntity basicHttpEntity = new BasicHttpEntity();
basicHttpEntity.setContent(new ByteArrayInputStream("token校验失败,请检查ex_proxy_token".getBytes()));
response.setEntity(basicHttpEntity);
} else {
// TODO 写入日志
String curl = Request2CurlUtil.getCurl(servletRequest);
SysQueryLogEntity log = new SysQueryLogEntity();
sysQueryLogDao.insert(log);
response = super.doExecute(servletRequest, servletResponse, proxyRequest);
}
return response;
}
/**
* 父类私有方法复制
* @param servletRequest
* @param proxyRequest
*/
private void setXForwardedForHeader(HttpServletRequest servletRequest, HttpRequest proxyRequest) {
if (this.doForwardIP) {
String forHeaderName = "X-Forwarded-For";
String forHeader = servletRequest.getRemoteAddr();
String existingForHeader = servletRequest.getHeader(forHeaderName);
if (existingForHeader != null) {
forHeader = existingForHeader + ", " + forHeader;
}
proxyRequest.setHeader(forHeaderName, forHeader);
String protoHeaderName = "X-Forwarded-Proto";
String protoHeader = servletRequest.getScheme();
proxyRequest.setHeader(protoHeaderName, protoHeader);
}
}
/**
* 获取第一个路径
* @param path 路径参数
* @return 第一个路径
*/
private String getFirstPath(String path) {
path = path.substring(1, path.length());
int index = path.indexOf("/");
if (index > 0) {
return path.substring(0, index);
}
return path;
}
}
配置文件
这里由于必须要填写targetUri,而目标uri在service方法被修改过,所以这里乱填个null值就行了
@Configuration
public class ProxyServletConfiguration {
private final static String SERVLET_URL = "/proxy/*";
@Bean
public ServletRegistrationBean proxyServletRegistration() {
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyProxyServlet(), SERVLET_URL);
//设置网址以及参数
Map<String, String> params = ImmutableMap.of("targetUri", "null", "log", "true");
registrationBean.setInitParameters(params);
return registrationBean;
}
}
使用方式
启动服务后,调用localhost:8080/proxy/baidu,即会跳转到百度中;调用localhost:8080/proxy/hao123,即会跳转到hao123中。
更多推荐
已为社区贡献2条内容
所有评论(0)