Skip to content

Instantly share code, notes, and snippets.

@mattbennett
Last active November 2, 2023 10:38
Show Gist options
  • Save mattbennett/246057831ebb76dbd8c5 to your computer and use it in GitHub Desktop.
Save mattbennett/246057831ebb76dbd8c5 to your computer and use it in GitHub Desktop.
Nameko websocket test

About

A tiny nameko service that demonstrates usage of websockets, designed to test that websocket connections traverse an ELB correctly.

Install & Run

  • git clone git@gist.github.com:246057831ebb76dbd8c5.git
  • create new virtualenv (optional)
  • pip install -r requirements.txt
  • update config file (optional)
  • nameko run service --config config.yaml

Config

The two config variables are the "public" hostname and port. They're injected into the javascript so need to be accessible to the client.

Behaviour

Launch the service and open a browser pointing at it. The websocket connection should immediately connect, but will not reconnect if the connection is broken. Every 10 seconds the service will send a "ping" message to all connected clients. The "ping" message uniquely identifies the service instance from which it originated.

Testing ELB

The connection should be stable even through ELB. When ELB is load-balancing more than one service instance, the client should remain connected to the first instance it connected to.

Assuming this works, we should subsequently test that the connection remains active even after reducing the frequency of the "ping" messsages to less than the ELB timeout. If this doesn't work, we'll have to introduce a heartbeat.

PUBLIC_HOST: "localhost"
PUBLIC_PORT: 8000
nameko>=2.2.0
jinja2==2.8
from jinja2 import Template
from nameko.extensions import DependencyProvider
from nameko.timer import timer
from nameko.web.handlers import http
from nameko.web.websocket import WebSocketHubProvider, rpc
from werkzeug import Response
TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<style>
#console{
padding:5px;
border:1px solid black;
}
#console p {
margin:0;
}
.event {
color:#999;
}
.warning{
color: orange;
}
</style>
<title>Nameko Websocket Test</title>
</head>
<body>
<div id="wrapper">
<h1>Nameko Websocket Test</h1>
<button id="disconnect">Disconnect</button>
<div id="container">
<div id="console">
</div>
</div>
<script type="text/javascript">
function connect(){
var socket;
var host = "ws://{{host}}:{{port}}/ws";
try{
var socket = new WebSocket(host);
message('<p class="event">Socket Status: '+socket.readyState);
socket.onopen = function(){
message('<p class="event">Socket Status: '+socket.readyState+' (open)');
subscribe();
}
socket.onmessage = function(msg){
message('<p class="message">Received: '+msg.data);
}
socket.onclose = function(){
message('<p class="event">Socket Status: '+socket.readyState+' (Closed)');
}
} catch(exception){
message('<p>Error'+exception);
}
function subscribe() {
try{
var json_msg = `{
"method": "subscribe",
"data": {}
}`;
socket.send(json_msg);
message('<p class="event">Sent: '+text)
} catch(exception){
message('<p class="warning">');
}
}
function message(msg){
$('#console').append(msg+'</p>');
}
$('#disconnect').click(function(){
socket.close();
});
}
$(document).ready(function() {
if(!("WebSocket" in window)){
$('<p>This demo requires browser websocket support</p>').appendTo('#container');
return
}
connect();
});
</script>
</body>
</html>
"""
class Config(DependencyProvider):
def get_dependency(self, worker_ctx):
return self.container.config
class ContainerIdentifier(DependencyProvider):
def get_dependency(self, worker_ctx):
return id(self.container)
class WebsocketService(object):
name = "websockets"
container_id = ContainerIdentifier()
websocket_hub = WebSocketHubProvider()
config = Config()
@http('GET', '/')
def home(self, request):
host = self.config.get('PUBLIC_HOST', 'localhost')
port = self.config.get('PUBLIC_PORT', '8000')
payload = Template(TEMPLATE).render({'host': host, 'port': port})
return Response(payload, content_type="text/html")
@rpc
def subscribe(self, socket_id):
self.websocket_hub.subscribe(socket_id, 'test_channel')
return 'subscribed to test_channel'
@timer(10)
def ping(self):
self.websocket_hub.broadcast('test_channel', 'ping', {
'value': "ping from {}".format(self.container_id),
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment