当我们需要通过网页直接访问XenServer的vm console时,我们可以直接使用websocket建立和xenserver的连接,这种方式有个问题是xenserver的主机为了支持这种WebSocket的访问方式,会在Xenserver的主机上面起很多wsproxy,作为xenserver vm console和websocket的代理。若我们不希望Xenserver加大负担,我们希望实现自己的webproxy,然后访问Xenserver Vm console,下面就是这种方法的介绍。
思路很简单,两个步骤:
1、创建一个websocketproxy服务
2、基于这个服务访问Xenserver vm console
这个websocketproxy的实现基于netty(netty是一个高性能的异步事件IO框架),下面是关键代码:
收到消息时先进行判断,若是建立websocket的连接,则消息类型肯定是HttpRequest,因为websocket协议是基于Http协议的。简单来说就是将http协议进行升级,从而变为websocket协议。进而调用ensureTargetConnect函数,连接具体的VM Console。
在ensureTargetConnect函数中,我们先是建立和Xenserver的连接,当连接建立之后,再发送消息给Xenserver,这个消息采用类似HTTP格式的非标准HTTP协议,具体参见makeHeaders函数,若和具体的VM Console建立好连接,则Xenserver会返回一个HTTP 200的状态码,表示我们已经和VM Console 成功建立连接了,这时Xenserver会立马发送这个VM Console的协议版本号。
@Override
public void messageReceived(ChannelHandlerContext ctx, final MessageEvent e)
throws Exception {
Object msg = e.getMessage();
// An HttpRequest means either an initial websocket connection
// or a web server request
if (msg instanceof HttpRequest) {
handleHttpRequest(ctx, (HttpRequest) msg, e);
// A WebSocketFrame means a continuation of an established websocket connection
} else if (msg instanceof WebSocketFrame) {
handleWebSocketFrame(ctx, (WebSocketFrame) msg, e);
// A channel buffer we treat as a VNC protocol request
} else if (msg instanceof ChannelBuffer) {
handleVncDirect(ctx, (ChannelBuffer) msg, e, null);
}
}
private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req, final MessageEvent e) throws Exception {
// Allow only GET methods.
if (req.getMethod() != GET) {
Logger.getLogger(WebsockifyProxyHandler.class.getName()).info("Just support GET Method.");
return;
}
String upgradeHeader = req.getHeader("Upgrade");
if(upgradeHeader != null && upgradeHeader.toUpperCase().equals("WEBSOCKET")){
Logger.getLogger(WebsockifyProxyHandler.class.getName()).fine("Websocket request from " + e.getRemoteAddress() + ".");
// Handshake
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
this.getWebSocketLocation(req), "base64", false);
this.handshaker = wsFactory.newHandshaker(req);
if (this.handshaker == null) {
wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel());
} else {
// deal with a bug in the flash websocket emulation
// it specifies WebSocket-Protocol when it seems it should specify Sec-WebSocket-Protocol
String protocol = req.getHeader("WebSocket-Protocol");
String secProtocol = req.getHeader("Sec-WebSocket-Protocol");
if(protocol != null && secProtocol == null )
{
req.addHeader("Sec-WebSocket-Protocol", protocol);
}
this.handshaker.handshake(ctx.getChannel(), req);
}
ensureTargetConnection (e, true, null, req.getUri());
}
}
private void ensureTargetConnection(ChannelEvent e, boolean websocket, final Object sendMsg, String para)
throws Exception {
if(outboundChannel == null) {
String[] paras = para.split("&");
String[] uuids = paras[0].split("=");
String[] authids = paras[1].split("=");
// Suspend incoming traffic until connected to the remote host.
final Channel inboundChannel = e.getChannel();
inboundChannel.setReadable(false);
Logger.getLogger(WebsockifyProxyHandler.class.getName()).info("Inbound proxy connection from " + inboundChannel.getRemoteAddress()
+ " uuid=" + uuids[1] + " authid=" + authids[1] + ".");
// resolve the target
Console console = Websockify.ConsoleMap.get(uuids[1]);
final String location = console.getLocation(Websockify.Conn);
InetSocketAddress target = new InetSocketAddress(new URL(location).getHost(), 80);
// Start the connection attempt.
ClientBootstrap cb = new ClientBootstrap(cf);
cb.getPipeline().addLast("aggregator", new HttpChunkAggregator(65536));
if ( websocket ) {
cb.getPipeline().addLast("handler", new OutboundWebsocketHandler(e.getChannel(), trafficLock));
}
else {
cb.getPipeline().addLast("handler", new OutboundHandler(e.getChannel(), trafficLock));
}
ChannelFuture f = cb.connect(target);
outboundChannel = f.getChannel();
if ( sendMsg != null ) outboundChannel.write(sendMsg);
f.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
// Connection attempt succeeded:
// Begin to accept incoming traffic.
connectVmConsole(location);
inboundChannel.setReadable(true);
Logger.getLogger(WebsockifyProxyHandler.class.getName()).info("Created outbound connection to " + location + ".");
} else {
Logger.getLogger(WebsockifyProxyHandler.class.getName()).severe("Failed to create outbound connection to " + location + ".");
inboundChannel.close();
}
}
});
} else {
if ( sendMsg != null ) outboundChannel.write(sendMsg);
}
}
private void connectVmConsole(String location) throws BadServerResponse, XenAPIException, XmlRpcException, MalformedURLException {
URL uri = new URL(location);
String headers[] = makeHeaders(uri.getPath().concat("?").concat(uri.getQuery()), uri.getHost(), Websockify.Conn.getSessionReference());
for (String header : headers) {
ChannelBuffer msgdata = new BigEndianHeapChannelBuffer(header.getBytes());
outboundChannel.write(msgdata);
outboundChannel.write(new BigEndianHeapChannelBuffer("\r\n".getBytes()));
}
}
private String[] makeHeaders(String path, String host, String session) {
String[] headers = { String.format("CONNECT %s HTTP/1.1", path),
String.format("Host: %s", host),
String.format("Cookie: session_id=%s", session), "" };
return headers;
}
参考资料:
http://docs.vmd.citrix.com/XenServer/5.6.0fp1/1.0/en_gb/sdk.html#retrieving_vnc_consoles_with_api
http://blogs.citrix.com/2011/02/11/xenserverconsole-examples/