49c90739de
For now Zaqar's websocket transport can only send and receive text messages in JSON format. By using messages in binary format it's possible to reduce network traffic between Zaqar server and it's clients. In most cases it's also possible to increase performance. This patch implements support for binary messages over websocket transport in Zaqar server. The MessagePack format was chosen for encoding/decoding messages as it's effective enough and available in convenient libraries for most programming languages. This patch also modifies "examples/websockets.html" example to be able to send and receive binary messages. All "print" function calls are substituted to local logger calls. DocImpact APIImpact The patch adds new functionality. Now sending binary request over websocket doesn't automatically return response with code 400, because now websocket transport is able to process binary requests encoded in MessagePack. blueprint: websocket-binary-support Change-Id: I07a7c46795e3b510ee397a6d2c4665e770c2c4b2
310 lines
12 KiB
HTML
310 lines
12 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<html>
|
||
<head>
|
||
<title>Zaqar WebSocket example</title>
|
||
<meta charset='utf-8' />
|
||
<link rel='stylesheet' href='http://yui.yahooapis.com/pure/0.6.0/pure-nr-min.css' />
|
||
<script type='text/javascript' src='http://code.jquery.com/jquery-2.1.4.min.js'></script>
|
||
<!--[if lte IE 9]>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.1.10/es5-shim.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/json3/3.3.2/json3.min.js"></script>
|
||
<![endif]-->
|
||
<script src="https://rawgithub.com/kawanet/msgpack-lite/master/dist/msgpack.min.js"></script>
|
||
<style>
|
||
.pure-g > div {
|
||
box-sizing: border-box;
|
||
}
|
||
#title h1 {
|
||
text-align: center;
|
||
border-bottom: 1px solid #ccc;
|
||
margin: 0;
|
||
padding: 1em;
|
||
}
|
||
|
||
#login {
|
||
background-color: #c7e8f2;
|
||
padding-left: 1em;
|
||
}
|
||
#queues {
|
||
padding-left: 1em;
|
||
background-color: #bbb;
|
||
min-height: 800px;
|
||
}
|
||
#messages {
|
||
padding-left: 1em;
|
||
}
|
||
#log {
|
||
font-size: 80%;
|
||
}
|
||
#right-col {
|
||
padding-left: 1em;
|
||
background-color: #f7e699;
|
||
min-height: 800px;
|
||
}
|
||
</style>
|
||
<script type='text/javascript'>
|
||
// Parameters:
|
||
var server_url = 'ws://localhost:9000/';
|
||
var project_id = 'cf38008b72d04b89a505b9d66d1d5768';
|
||
var client_id = '31209ff3-ba03-4cec-b4ca-655f4899f8f4';
|
||
var send_binary = true;
|
||
|
||
var socket = new WebSocket(server_url);
|
||
socket.binaryType = 'arraybuffer';
|
||
|
||
if (send_binary == true) {
|
||
// Use MessagePack(binary) for encoding messages
|
||
encode = function(data) {
|
||
return msgpack.encode(data);
|
||
}
|
||
} else {
|
||
// Use JSON(text) for encoding messages
|
||
encode = function(data) {
|
||
return JSON.stringify(data);
|
||
}
|
||
}
|
||
msgpack_decode = function(enc_data) {
|
||
return msgpack.decode(new Uint8Array(enc_data));
|
||
}
|
||
json_decode = function(enc_data) {
|
||
return JSON.parse(enc_data);
|
||
}
|
||
add_connection_info = function(msg) {
|
||
msg += ". Using parameters: Server URL: " + server_url;
|
||
msg += ". Project ID: " + project_id;
|
||
msg += ". Client ID: " + client_id;
|
||
msg += ". Binary communication: " + send_binary;
|
||
return msg
|
||
}
|
||
log_info = function(msg) {
|
||
var node = document.createElement('div');
|
||
var date = new Date().toUTCString();
|
||
msg = date + " " + msg;
|
||
node.appendChild(document.createTextNode(msg));
|
||
$('#log').append(node);
|
||
}
|
||
socket.onopen = function(evt) {
|
||
msg = "Connection opened";
|
||
msg = add_connection_info(msg);
|
||
log_info(msg);
|
||
}
|
||
socket.onclose = function(evt) {
|
||
msg = "Connection closed";
|
||
log_info(msg);
|
||
}
|
||
socket.onerror = function(evt) {
|
||
msg = "Connection error";
|
||
msg = add_connection_info(msg);
|
||
log_info(msg);
|
||
}
|
||
socket.onmessage = function(evt) {
|
||
if (evt.data instanceof ArrayBuffer) {
|
||
// Received payload in MessagePack(binary) format
|
||
var data = msgpack_decode(evt.data);
|
||
}
|
||
if (typeof evt.data === "string") {
|
||
// Received payload in JSON(text) format
|
||
var data = json_decode(evt.data);
|
||
}
|
||
if ('request' in data && 'headers' in data) {
|
||
// Response received
|
||
var action = data["request"]["action"];
|
||
msg = "action: " + action;
|
||
msg += " status: " + data["headers"]["status"];
|
||
msg += " body: " + JSON.stringify(data["body"]);
|
||
log_info(msg);
|
||
if (action == 'queue_list') {
|
||
var queues = data['body']['queues'];
|
||
display_queues(queues);
|
||
} else if (action == 'message_list') {
|
||
var messages = data['body']['messages'];
|
||
display_messages(messages);
|
||
} else if (action == 'queue_create' || action == 'queue_delete') {
|
||
list_queues();
|
||
} else if (action == 'authenticate' && data["headers"]["status"] == 200) {
|
||
list_queues();
|
||
} else if (action == 'message_post' || action == 'message_delete') {
|
||
list_messages();
|
||
}
|
||
} else {
|
||
// Can be notification or unexpected data
|
||
if(data.hasOwnProperty('body')){
|
||
msg = "notification: " + JSON.stringify(data);
|
||
log_info(msg)
|
||
list_messages();
|
||
} else {
|
||
msg = "unexpected data: " + JSON.stringify(data);
|
||
log_info(msg)
|
||
}
|
||
}
|
||
}
|
||
login = function(frm) {
|
||
var data = {
|
||
'auth': {
|
||
'identity': {
|
||
'methods': ['password'],
|
||
'password': {
|
||
'user': {
|
||
'name': frm['user'].value,
|
||
'domain': {'id': 'default'},
|
||
'password': frm['password'].value
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
$.ajax({
|
||
'type': 'POST',
|
||
'url': 'http://localhost:5000/v3/auth/tokens',
|
||
'data': JSON.stringify(data),
|
||
'contentType': 'application/json',
|
||
'dataType': 'json',
|
||
'success': function(data, code, response) {
|
||
var token = response.getResponseHeader('X-Subject-Token')
|
||
if (token == null){
|
||
log_info("Connected to Keystone, but no 'X-Subject-Token' "
|
||
+ "header was provided. Keystone's CORS filter is probably "
|
||
+ "not configured to expose this header.");
|
||
} else {
|
||
log_info("Got token from Keystone. " +
|
||
"Sending authentication request to Zaqar.");
|
||
var msg = {'action': 'authenticate',
|
||
'headers': {'X-Auth-Token': token,
|
||
'Client-ID': client_id,
|
||
'X-Project-ID': project_id}}
|
||
socket.send(encode(msg));
|
||
};
|
||
},
|
||
'error': function(data, code, errorThrown) {
|
||
if (errorThrown) {
|
||
log_info("Keystone error: " + errorThrown);
|
||
} else {
|
||
log_info("Failed to connect to Keystone. Keystone is either" +
|
||
" offline, or CORS is not enabled in Keystone.");
|
||
}
|
||
|
||
}
|
||
});
|
||
return false;
|
||
}
|
||
send_message = function(action, body) {
|
||
var msg = {'action': action,
|
||
'headers': {'Client-ID': client_id, 'X-Project-ID':
|
||
project_id}};
|
||
if (body) {
|
||
msg['body'] = body;
|
||
};
|
||
socket.send(encode(msg));
|
||
}
|
||
list_queues = function() {
|
||
send_message('queue_list');
|
||
}
|
||
create_queue = function(frm) {
|
||
send_message('queue_create', {'queue_name': frm['queue'].value});
|
||
return false;
|
||
}
|
||
get_selected_queue = function() {
|
||
var queue_select = $('#queue_list');
|
||
return queue_select.val();
|
||
}
|
||
display_queues = function(queues) {
|
||
var queue_select = $('#queue_list');
|
||
queue_select.empty();
|
||
$.each(queues, function(idx, queue) {
|
||
queue_select.append('<option>' + queue.name + '</option>');
|
||
});
|
||
}
|
||
display_messages = function(messages) {
|
||
var table_body = $('#message_list tbody');
|
||
table_body.empty();
|
||
$.each(messages, function(idx, message) {
|
||
table_body.append('<tr><td>' + message.age + '</td><td>' + message.body + '</td><td>' + message.ttl + '</td><td><button class=\'pure-button\' onclick=\'delete_message("' + message.id + '")\'>Delete</button></td></tr>');
|
||
});
|
||
}
|
||
delete_queue = function() {
|
||
send_message('queue_delete', {'queue_name': get_selected_queue()});
|
||
}
|
||
list_messages = function() {
|
||
send_message('message_list', {'queue_name': get_selected_queue(), 'echo': true});
|
||
}
|
||
queue_message = function(frm) {
|
||
var body = frm['body'].value;
|
||
var ttl = parseInt(frm['ttl'].value);
|
||
send_message('message_post', {'queue_name': get_selected_queue(), 'messages': [{'body': body, 'ttl': ttl}]});
|
||
return false;
|
||
}
|
||
delete_message = function(message_id) {
|
||
send_message('message_delete', {'queue_name': get_selected_queue(), 'message_id': message_id});
|
||
}
|
||
subscribe_queue = function() {
|
||
send_message('subscription_create', {'queue_name': get_selected_queue(), 'ttl': 3600});
|
||
}
|
||
</script>
|
||
</head>
|
||
<body>
|
||
<div id='title'>
|
||
<h1>Zaqar WebSocket example</h1>
|
||
</div>
|
||
|
||
<div id='login'>
|
||
<form class='pure-form' onsubmit='return login(this)'>
|
||
<fieldset>
|
||
<input type='text' name='user' placeholder='User' />
|
||
<input type='password' name='password' placeholder='Password' />
|
||
<button class='pure-button' type='submit'>Login</button>
|
||
</fieldset>
|
||
</form>
|
||
</div>
|
||
|
||
<div class='pure-g'>
|
||
<div id='queues' class='pure-u-1-3'>
|
||
<h4>Queues</h4>
|
||
<form class='pure-form' onsubmit='return create_queue(this)'>
|
||
<fieldset>
|
||
<input type='text' name='queue' placeholder='Queue name' />
|
||
<button class='pure-button' type='submit'>Create</button>
|
||
</fieldset>
|
||
</form>
|
||
<form class='pure-form' onsubmit='return false'>
|
||
<fieldset>
|
||
<select id='queue_list'></select>
|
||
<button class='pure-button' onclick='delete_queue()'>Delete</button>
|
||
<button class='pure-button' onclick='list_messages()'>List messages</button>
|
||
<button class='pure-button' onclick='subscribe_queue()'>Subscribe</button>
|
||
<button class='pure-button' onclick='list_queues()'>Refresh</button>
|
||
</fieldset>
|
||
</form>
|
||
</div>
|
||
|
||
<div id='messages' class='pure-u-1-3'>
|
||
<h4>Messages</h4>
|
||
<form class='pure-form' onsubmit='return queue_message(this)'>
|
||
<fieldset>
|
||
<input type='text' name='body' placeholder='Message body' />
|
||
<input type='text' name='ttl' size='5' value='3600' placeholder='TTL' />
|
||
<button type='submit' class='pure-button'>Post</button>
|
||
</fieldset>
|
||
</form>
|
||
<table class='pure-table pure-table-horizontal' id='message_list'>
|
||
<thead>
|
||
<tr>
|
||
<th>Age</th>
|
||
<th>Body</th>
|
||
<th>TTL</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class='pure-u-1-3' id='right-col'>
|
||
<h4>Logs</h4>
|
||
<div id='log'></div>
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html>
|