no-vnc and node.js to realize web remote desktop

Introduction

Project requirements require remote desktop access on the browser side, as shown in the figure:

To achieve remote desktop, we need to rely on VNC protocol:

VNC (Virtual Network Computing) is a screen picture sharing and remote operation software using RFB protocol. This software can send keyboard and mouse actions and instant screen pictures through the network.

Relevant references are relatively few. Most articles searched by Google are about how to use the client to build and access VNC. Few articles are embedded in the web. Tencent cloud has related functions, but because of business security, we can not see how people can achieve it.

Goodbye, Baidu. After checking with Baidu, I realized that VNC was lipstick.

So the way of VNC practice is as follows:

  1. According to their own knowledge and skills, design a VNC program.
  2. Try and analyze the feasibility.
  3. Modify the details of the scheme according to the feasibility, or redesign the overturn scheme.

From the initial design of the whole to the final landing scheme, there are about seven iterations of the following schemes:

  1. SpringBoot calls the C++ class library of REALVNC and interacts with the background and front. Failure, because REALVNC is too expensive for customers to afford.
  2. In SpringBoot, JavaViewer is implemented by imitating TightVNC to obtain data and interact with the background and front. Failed because the source code of TightVNC JavaViewer was not commented and could not understand.
  3. Handwritten VNC client in SpringBook, front and back data interaction. Failure, because implementing a protocol from zero is too complex and time-consuming.
  4. The browser side only makes VNC links and uses native client to access the host directly. Failure, need to install software, and can only access the local area network host.
  5. Native client + nginx data forwarding. Failure, need to install software, can not achieve dynamic forwarding (can not dynamically change the nginx configuration file).
  6. no-vnc + nginx data forwarding. Failed to dynamically forward (unable to dynamically change nginx configuration file).
  7. no-vnc + node.js data forwarding. Success, perfect realization.

Realization

thought

The overall idea is as follows: nginx forwarding front-end websocket connection, in order to achieve external network forwarding, add the developed node.js server as a proxy, and forward the browser-side no-vnc websocket datagram to the target host in the transport layer.

why nginx ?

If you think about it, you can actually realize the function without nginx. The main reason for using nginx here is to reduce the coupling between the foreground and the background architecture.

Adding gateways to forward all requests exposes only one port to the front desk, regardless of the technology used in the back desk, the architecture used, and the micro services used, as if it were accessing a single application in the front desk.

Just like the current HuaSoft project, the backstage uses spring-boot, net, node.js. Each language framework has its own advantages and links the modules through nginx forwarding. No matter how the backstage architecture changes, it has no impact on the front desk. This should be the best practice of micro-service architecture.

This is the micro-service architecture diagram recommended by spring. We learned and practiced the api gateway. Spring recommended netflix zuul. We used nginx, which has the same performance in request forwarding.

With the growth of business demand, we will certainly also split services, register services, discover services, message queues, RPC calls. Then we explore the best practices of spring-cloud with eureka, zookeeper, hystrix, feign and other excellent open source components.

websocket

I have never known websocket before, that is, I know the name, but I have not learned the details.

http protocol: request response, client request, server response, one request is over. The server can not actively push data to the client.

To solve this problem, websocket came into being. If shown, do not elaborate.

no-vnc

Official website links: noVNC

Installation Dependency:

npm install @novnc/novnc

Front-end component

An empty div is also referenced in the component.

<div class="container" #container>
</div>
@ViewChild('container')
private container: ElementRef<HTMLDivElement>;

The core code is actually just these lines, and all protocol details are encapsulated in the RFB class in no-vnc.

All descriptions take port 5900 accessing 192.168.0.104 host as an example. The websocket address is ws://127.0.0.1:8013/vnc/192.168.0.104:5900.

/**
 * VNC Connect
 */
private VNCConnect(): void {
    /** Access/vnc/websocket */
    const url = `ws://${this.host}/vnc/${this.ip}:${this.port}`;

    /** New remote control object */
    this.rfb = new RFB(this.container.nativeElement, url, {
        credentials: {
            password: this.password,
        },
    });

    /** Add connect event listener */
    this.rfb.addEventListener('connect', () => {
        this.rfb.focus();
    });
}

nginx forwarding

nginx listens on the local port 8013.

ws://127.0.0.1:8013/vnc/192.168.0.104:5900 request was sent to nginx, according to the prefix matching, forwarded to port 8112 at the beginning of/vnc/

location /vnc/ {
    proxy_pass http://127.0.0.1:8112/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
}

node.js forwarding

node.js listens on port 8112 to process current websocket requests.

/** Establishment of Web socket server based on vnc_port */
const vnc_server = http.createServer();
vnc_server.listen(vnc_port, function () {
    const web_socket_server = new WebSocketServer({server: vnc_server});
    web_socket_server.on('connection', web_socket_handler);
});

The core code for forwarding is in the method web_socket_handler. The following is the complete code:

Let's just say that previous comments are not standard. All comments should be document comments. Single-line comments use the format of /** content */.

/** Introducing http packages */
const http = require('http');

/** Introducing net package */
const net = require('net');

/** Introducing websocket class */
const WebSocketServer = require('ws').Server;

/** Native ip address */
const localhost = '127.0.0.1';

/** Open vnc websocket forwarding port */
const vnc_port = '8112';

/** Print prompt information */
console.log(`Successful creation WebSocket agent : ${localhost} : ${vnc_port}`);

/** Establishment of Web socket server based on vnc_port */
const vnc_server = http.createServer();
vnc_server.listen(vnc_port, function () {
    const web_socket_server = new WebSocketServer({server: vnc_server});
    web_socket_server.on('connection', web_socket_handler);
});

/** websocket processor */
const web_socket_handler = function (client, req) {
    /** Get the request url */
    const url = req.url;

    /** Intercepting host address */
    const host = url.substring(url.indexOf('/') + 1, url.indexOf(':'));

    /** Intercept port number */
    const port = Number(url.substring(url.indexOf(':') + 1));

    /** Print logs */
    console.log(`WebSocket Connect : Edition ${client.protocolVersion}, Agreement ${client.protocol}`);

    /** Connect to VNC Server */
    const target = net.createConnection(port, host, function () {
        console.log('Connect to the target host');
    });

    /** Data Events */
    target.on('data', function (data) {
        try {
            client.send(data);
        } catch (error) {
            console.log('The client is closed and the connection to the target host is cleaned up');
            target.end();
        }
    });

    /** end event */
    target.on('end', function () {
        console.log('Target host closed');
        client.close();
    });

    /** Error events */
    target.on('error', function () {
        console.log('Target Host Connection Error');
        target.end();
        client.close();
    });

    /** Message Events */
    client.on('message', function (msg) {
        target.write(msg);
    });

    /** Closing events */
    client.on('close', function (code, reason) {
        console.log(`WebSocket The client disconnects: ${code} [${reason}]`);
        target.end();
    });

    /** Error events */
    client.on('error', function (error) {
        console.log(`WebSocket Client error: ${error}`);
        target.end();
    });
};

summary

After half a month of worrying about this function and sleeping poorly, the functions that customers have seen on Tencent Cloud are particularly uncomfortable if they can't write them out. Now they have finally solved it satisfactorily.

Embrace open source and help each other. If I could see this blog in the past, I would not waste so much time trying again and again.

In the evening, there are written tests with byte beating. The problem of byte beating is very difficult. Come on, my friends.

Tags: node.js vnc Nginx network Spring

Posted on Sun, 11 Aug 2019 01:40:39 -0700 by d-woo