“targetUri”, “null”, //这里写null是因为targetUri是在自定义的VNCProxyServlet类中动态传入的,而且这里必须要有值
ProxyServlet.P_LOG, “true”,
ProxyServlet.P_PRESERVEHOST,“true”,
ProxyServlet.P_PRESERVEcookieS,“true”
);
servletRegistrationBean.setInitParameters(params);
return servletRegistrationBean;
}
}
这里遇到的坑:
刚开始其实是准备在已有的一个模块中加上这个代理功能,因为可以指定拦截的路径,比如只拦截请求路径为**/proxyproxy***,这样所有请求到这个模块的都会被代理。
- 自定义Servlet实现动态代理目标地址
// VNCProxyServlet继承了ProxyServlet 重写了service方法 在方法中添加自定义操作 从请求地址中动态获取
@Override
protected void service(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws ServletException, IOException {
// 获取请求地址
String targetUri = servletRequest.getRequestURL().toString();
// 正则取请求地址中的参数 参数是放在域名中的
Matcher matcher = DOMAIN_REX.matcher(targetUri);
if(!matcher.find()){
// 自定义的异常
throw new GenericException(“从域名中获取vmId异常!”);
}
// 取域名中的第一个 eg: http://vmId.xxx.cn得 [vmId,xxx,cn] 得 vmId
Long vmId = Long.valueOf(matcher.group().split(".")[0]);
// eg:业务逻辑根据vmId去拿 targetUri
targetUri = vmService.getTargetUrl(vmId);
if (StringUtils.isEmpty(targetUri)) {
throw new GenericException(“代理路径不正确,请确认路径”);
}
// 设置Url
if (servletRequest.getAttribute(ATTR_TARGET_URI) == null) {
servletRequest.setAttribute(ATTR_TARGET_URI, targetUri);
}
// 设置Host
if (servletRequest.getAttribute(ATTR_TARGET_HOST) == null) {
URL trueUrl = URLUtil.url(targetUri);
servletRequest.setAttribute(ATTR_TARGET_HOST, new HttpHost(trueUrl.getHost(), trueUrl.getPort(), trueUrl.getProtocol()));
}
// 下面大部分都是父类的源码 没有需要特别修改的地方
String method = servletRequest.getMethod();
// 替换多余路径
String proxyRequestUri = this.rewriteUrlFromRequest(servletRequest);
HttpRequest proxyRequest;
if (servletRequest.getHeader(HttpHeaders.CONTENT_LENGTH) != null ||
servletRequest.getHeader(HttpHeaders.TRANSFER_ENCODING) != null) {
proxyRequest = new BasicHttpRequest(method, proxyRequestUri);
} else {
proxyRequest = this.newProxyRequestWithEntity(method, proxyRequestUri, servletRequest);
}
this.copyRequestHeaders(servletRequest, proxyRequest);
setXForwardedForHeader(servletRequest, proxyRequest);
HttpResponse proxyResponse = null;
try {
// Execute the request
proxyResponse = this.doExecute(servletRequest, servletResponse, proxyRequest);
// Process the response
int statusCode = proxyResponse.getStatusLine().getStatusCode();
// “reason phrase” is deprecated but it’s the only way to pass
servletResponse.setStatus(statusCode, proxyResponse.getStatusLine().getReasonPhrase());
// copying response headers to make sure SESSIonID or other cookie which comes from remote server
// will be saved in client when the proxied url was redirected to another one.
copyResponseHeaders(proxyResponse, servletRequest, servletResponse);
if (statusCode == HttpServletResponse.SC_NOT_MODIFIED) {
servletResponse.setIntHeader(HttpHeaders.CONTENT_LENGTH, 0);
} else {
copyResponseEntity(proxyResponse, servletResponse, proxyRequest, servletRequest);
}
} catch (Exception e) {
handleRequestException(proxyRequest, proxyResponse, e);
} finally {
if (proxyResponse != null) {
EntityUtils.consumeQuietly(proxyResponse.getEntity());
}
}
}
这里主要列出关键部分,因为方案还没有完结。
- 问题
本以为这样就成功了,但是测试之后发现页面和静态资源都代理过去了,但是有一个websocket请求失败了。像noVNC这种网页版的黑窗口,早就该想到肯定是用websocket这种长链接的请求进行交互的。后来去搜了一下这个叫websockify的请求,就是最开始介绍noVNC博客中介绍的:
浏览器不支持VNC,所以不能直接连接VNC,但是可以使用代理,使用noVNC通过WebSocket建立连接,而VNC Server不支持WebSocket,所以需要开启Websockify代理来做WebSocket和TCP Socket之间的转换,这个代理在noVNC的目录里,叫做websockify。
此时项目是能够拦截到websockify这个请求的,但是由于servlet把这个请求当成普通的请求去代理到目标服务器,这样是无法成功的,所以要做的就是类似实现一个websocket的反向代理,搜了一下的话发现例子不是很多,大多都是在前端做的,前端作为客户端与服务端建立websocket连接,但目前的状况很明显是需要这个代理模块既做websocket服务端与web端建立连接,再作为websocket客户端与VNC 服务端建立连接,然后进行交互传递通信。
后面也找到了这篇博
【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】 浏览器打开:qq.cn.hn/FTf 免费领取
客[通过noVNC和websockify连接到QEMU/KVM](
),然后总结一下从用户发出请求到得到响应的流程:
PC Chrome(客户端) => noVNC Server(noVNC端) => websockify(websocket转TCP Socket) => VNC Server(VNC服务端) => websockify(TCP Socket转websocket) => noVNC Server(noVNC端)=> PC Chrome(客户端)
用户使用PC Chrome浏览器请求 noVNC端(因为无法直接访问VNC Server端,VNC Server是不支持Websocket连接),经由websockify将websocket转为TCP Socket请求到VNC服务端,返回TCP响应,经由websockify转换为websocket返回给客户端浏览器,这样来进行交互。整个过程 websockify 代理器是关键,noVNC 可以被放在浏览器端。
- noVNC网页端与代理模块建立websocket通信
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
@ServerEndpoint("/websockify")
@Component
public class WebSocketServer {
@OnOpen
public void onOpen(Session session) {
logger.info(“open…”);
}
@OnClose
public void onClose() {
logger.info(“close…”);
}
@OnMessage
public void onMessage(String message, Session session) {
logger.info(message);
}



