📜 ⬆️ ⬇️

HTML + JS client for Line video surveillance system



On Geektimes I often meet and enjoy reading posts from the DIY series. Having decided to make a small contribution to the treasury of valuable experience gathered here, I am going to describe in detail the process of creating a client for the web based on the Line servers.

The video surveillance system "Line" provides an open API, and the developers claim that based on it, you can write your own client to view the video archive and cameras online. Also, if you wish, you can implement such functions as adding events to the archive, OSD overlaying video. A description of all the features presented in the specification on the official site .

This article is a real example, as I, a user with basic knowledge of JS, HTML, wrote my own application that implements the basic principles of working with Line servers through the embedded web server.

Input data

The author is a newcomer to the development of an HTML client, and is related to the development of the Liniya video surveillance system.
The level of knowledge of JS, HTML - the initial.
The task is to write an HTML client for working with devices based on Line software using the specifications from the site.

I will reveal the main intrigue immediately - I came to two conclusions:

  1. The specification is real, described quite clearly, you can write a client using C ++, PHP.
  2. A full-fledged HTML client, using only JS, cannot be written - only online observation is up to the RPC specification.

The first conclusion is quite natural, given the large number of integrations with third-party programs. All of them are described on the website: there are access control systems , weighing , POS-systems , programs for determining license plates and 1C .

The second conclusion is more interesting, consider it below.

Why it is impossible to create a full-fledged client on HTML + JS?


Answer: cross-domain requests.

At the moment, the Lines web server is limited, and by simply copying the code into the www folder, access cannot be obtained. However, the developers promise that in the new version for Linux and in “Line 8.0” the web server will work as standard: in case of a request, if there is a file, it will return it.

Now we are creating a new project and starting to code. Like all newbies in web programming, specifying that the Line server responds with "*" in the Access-Control-Allow-Origin header, I began to work hard on the code, checking the result on Firefox 57.0.4 (64-bit). Requests to the server sent XMLHttpRequest.

Initially it would be useful to study the information on this resource . Everything was described there in great detail, but I really wanted to complete the task as quickly as possible. And, unfortunately, due to the lack of information, half a day was lost on beating his head against the wall of security policies of modern browsers.

At the time of this writing, four main modern browsers do not allow to read the headers received from the server. According to the specification, it is necessary to implement Digest-authorization, which is impossible without headers.

By the end of the first day, I realized that without adding OPTIONS processing to the “Lines” web server, the browser does a OPTIONS pre-request for requests with a “hard” method or special headers, specifying them in the Access-Control-Request-Method and Access-Control-Request-Headers. Therefore, I began to look for other authorization options, but the real Basic or Digest did not take off.

An alternative method has already been described in the specification, it remained to spend some time in correspondence with the program department of "Line". Since such difficulties arise not for the first time, there is already a crutch for authorization, and it is even mentioned in the specification:
On clients where it is impossible to authorize the request by standard means (HTTP Digest / Basic Authentication), the Authorization header can be transmitted by one of the request parameters, for example
/kfd3ado1sdrms/streaming/main.flv?authorization=Basic%20d2ViOg==

After all the manipulations, the standard cross-domain query was correctly executed! You must also add the Accept header with the correct type to the request - I decided to use JSON.

Request Code:

function get_request_url(method,current_server_data, resource, additional){ var request = current_server_data.server_ip + ':' +current_server_data.port +resource+'?authorization=Basic '+ utf8_to_b64(current_server_data.user+':'+current_server_data.password); if (additional != '' && typeof additional != "undefined") { request += '&' + additional; } return request; } function http_request_of_resource (server_index , resource, auth_attempt) { var request = get_request_url('GET', servers_array[server_index], resource,''); var req_ = new XMLHttpRequest(); req_.open('GET', 'http://'+ request, true); //req_.timeout = 9000; // выполнить код, когда придёт ответ req_.onreadystatechange = function() { if (this.readyState == 2) { if (this.status == 401) { //console.log('---unauthorized'); hideModal(); update_nessecary_structure(resource, 'unauthorized', server_index); } } else if (this.readyState === 4) { if (this.status === 0) { hideModal(); update_nessecary_structure(resource, 'server_down',server_index) } if (this.status == 200) { if (auth_attempt) hideModal(); else resource =(resource =='/cameras') ? resource+'_update_info': resource; //console.log('200' + this.responseText); update_nessecary_structure(resource, this.responseText, server_index); } else if (this.status == 404) { //console.log('404'); update_nessecary_structure(resource, '404', server_index); } } }; // Оправка запроса req_.setRequestHeader('Content-type', 'text/plain; charset=utf-8'); req_.setRequestHeader('Accept', 'application/json'); req_.send(); } 

We change the resource to the one we need according to the specification and get some data. The variable additional contains additional parameters for the query, if any. At this, mastering the first half of the specification, namely receiving / sending text data through GET requests, can be considered closed.

Next, I was faced with the fact that the IMG tag in IE does not play the MJPEG stream and I need to independently implement the update of the images from the cameras. The code is open, it can be viewed and changed if desired. In the current implementation, simultaneous playback of a maximum of six MJPEG streams is available, so work with the view that displays more cameras will have to be done by ourselves. All this is in the example , if you want, you can find and understand, but if you have any questions, be sure to ask in the comments.

RPC specification


We are offered to send and receive data either in JSON (server version "Lines 7.1.1" and higher) or MessagePack (version "Line 7.0" and higher). It is mentioned that MessagePack weighs less and works faster, but, honestly, I would choose JSON (it is already built in JS), if not for one thing but in the specification: getting frames from the archive is possible only in MessagePack. I had to go to their official website and download the JS-file, which has onboard methods encode and decode.

The request sending function is ready! But it is too early to celebrate victory: when you try to change the request header of the Content-type, the browser swears and does not send data to the server. The fact is that the Lines server analyzes this field and performs parsing, depending on the type. On my own I could not do further.

I sent a request to the program department, and after the discussion I was added a crutch, as in the case of authorization, the Content-type will be transmitted in the url request:

 function rpc_request_of_resource (current_server_data , rpc_method, rpc_request) { var request = get_request_url('POST', current_server_data, '/rpc',''); //console.log("i'm here request = " + request + ' '+ current_server_data.user); request += "&content-type='application/x-msgpack'"; var req_ = new XMLHttpRequest(); req_.open('POST', 'http://'+ request, true); // выполнить код, когда придёт ответ req_.responseType = 'arraybuffer'; req_.onreadystatechange = function() { if (this.readyState == 2) { if (this.status == 401) { //console.log('401' + this.getAllResponseHeaders()); console.log('unauthorized'); } } else if (this.readyState == 4) { if (this.status == 200) { //if (auth_attempt) hideModal(); //console.log('200' + this.responseText); rpc_update_nessecary_method(rpc_method, this.response); } else if (this.status == 404) { console.log('404'); } else if (this.status == 500) { //console.log('500'); rpc_update_nessecary_method(rpc_method, '500'); } } }; // Оправка запроса //req_.setRequestHeader('Content-type', 'text/plain; charset=utf-8'); //req_.setRequestHeader('Content-type', 'application/x-msgpack'); req_.setRequestHeader('Accept', 'application/x-msgpack'); req_.send(rpc_request); } 

This change will work from the version "Line 7.4.1" and higher. For all servers below this version, working with the resource / rpc will be unavailable.

In the end, I want to thank all the clients who sent us questions / suggestions related to the implementation of applications based on our API. Thanks to you, a study was conducted in which some shortcomings were identified and corrected.

The example described in this article will gradually grow into the full-fledged HTML-client of the Line. All code will be readable, you can change it or use it as a basis for building your own solutions. API will eventually be filled with even more opportunities, which we will definitely inform.

Source: https://habr.com/ru/post/409999/