I have a small server in Python that I'm trying to get to terminate based on a message it gets from a client. This is the server:
async def receiver(websocket, path):
received_data = await websocket.recv()
print("< {}".format(received_data))
if received_data == "Order66":
websocket.keep_running = False
asyncio.get_event_loop().stop()
else:
print("< {}".format(received_data))
def start_the_server():
start_server = websockets.serve(receiver, 'localhost', 8765)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
def main():
start_the_server()
if __name__ == "__main__":
main()
The "client" is a larger app which also has a Javascript API and I am trying to this:
// send the info and disconnect
const socket2 = new WebSocket('ws://localhost:8765');
socket2.addEventListener('open', function (event) {
socket2.send(stats);
socket2.send("Order66");
});
However, it only ever sends "stats" (in this case it's a string), and the second send(...) never seems to get sent.
Related
First time poster here. I am trying to put together a chatroom app using Flask-SocketIO and am having a hard time getting one tiny, but crucial, piece to work. When I call the emit method in the server and set the broadcast=True flag, the expected behavior is that it triggers the respective socket.on events on the client side for all users, including the sender. However, my code apparently only does so for all clients other than the sender. This is contrary to the documentation at https://flask-socketio.readthedocs.io/en/latest/, where it states:
When a message is sent with the broadcast option enabled, all clients connected to the namespace receive it, including the sender. When namespaces are not used, the clients connected to the global namespace receive the message.
I need for this to send the message and trigger the socket.on event for the sender as well as all other clients. Can anyone explain what I'm doing wrong here? Because it seems like such a basic feature that I must be missing something really obvious.
Server code:
import os
import requests
from flask import Flask, jsonify, render_template, request
from flask_socketio import SocketIO, emit, join_room, leave_room
app = Flask(__name__)
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY')
socketio = SocketIO(app)
channels = ['a', 'b', 'c', 'testing', 'abc', '123']
ch_msgs = {'a': ['testing', '1234', 'high five'], 'b': ['hello']}
msg_limit = 100
#app.route('/')
def index():
return render_template('index.html')
#socketio.on('add channel')
def add(data):
new_channel = data
channels.append(new_channel)
ch_msgs[new_channel] = list()
emit('announce channel', new_channel, broadcast=True)
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
Javascript:
document.addEventListener('DOMContentLoaded', () => {
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port);
socket.on('connect', () => {
document.querySelector('#add_channel').onclick = () => {
const new_channel = document.querySelector('#new_channel').value;
socket.emit('add channel', new_channel);
};
});
socket.on('announce channel', data => {
const li = document.createElement('li');
li.innerHTML = '#' + `${data}`;
$('#channel_list').append(li);
});
});
HTML/CSS snippet:
<h3>Chat Channels</h3>
<br>
<ul id='channel_list' style='list-style-type:none;'>
</ul>
<form class='channel'>
<input type='text' id='new_channel' placeholder='add new channel'><input type='submit' id='add_channel' value='Add'>
</form>
All clients in fact are receiving the message sent, including the sender, but the issue here is that the sender's page is refreshed as soon as the form is submitted, (which is the default behavior of forms) thus resulting in loss of all local data.
In order to prevent this, you need to use event.preventDefault() method. In your case, you may change the event handler for connect event like this:
socket.on('connect', () => {
document.querySelector('#add_channel').onclick = event => {
event.preventDefault();
const textinput = document.querySelector('#new_channel')
const new_channel = textinput.value;
textinput.value = ""; // Clear input after value is saved
socket.emit('add channel', new_channel);
};
});
I am currently trying to use Tornado's web-socket handlers to update a dashboard every time a certain function is called. here is the handler:
class WebSocketHandler(websocket.WebSocketHandler):
clients = []
def open(self):
logging.info("WEBSOCKET OPEN")
WebSocketHandler.clients.append(self)
def on_message(self, message):
logging.info("message from websocket recieved")
self.write_message("WebSocket connected")
def on_close(self):
logging.info("WEBSOCKET closed")
and here is the client-side script which connects the WebSocket on load:
function WebSocketTest()
{ var ws = 0;
ws = new WebSocket("ws://localhost:8008/WEB");
ws.onopen = function()
{
ws.send("initial connect")
}
ws.onmessage = function (evt)
{
console.log(evt.data)
};
ws.onclose = function()
{
console.log("closed ");
};
}
The websockets connect sucessfully.
i need to call write_message from WebSocketHandler but i'm very confused what the instance of it is? the error i keep running into is that self isn't defined but im not sure what self is exactly? i know that WebSocketHandler gets run whenever the client tries to load ^/WEB$
EDIT: here is my server.py file, i need to call write_message right after the spawn callback call in itercheckers
class Server():
#classmethod
def run(cls):
options.parse_command_line()
# Start web
template_path = os.path.join(os.path.dirname(__file__), 'templates')
jinja2loader = Jinja2Loader(template_path)
kwargs = dict(
template_loader=jinja2loader,
static_path=os.path.join(os.path.dirname(__file__), 'static'),
debug=True,
login_url="/auth/login",
cookie_secret="dn470h8yedWF9j61BJH2aY701i6UUexx"
)
app = web.Application(handlers, **kwargs).listen(
configuration['server']['port'],)
# Reset events
#gen.coroutine
def reset(parent=None):
if parent is None:
parent = configuration
# Reset event happyness
yield events.reset_happy(parent)
# Read last status
data = yield events.get(parent)
# Read and set happy from the last status
happy = (data or {}).get('status', events.STATUS_OK) \
in events.HAPPY
yield events.set_happy(parent, happy)
# Iterate sub-events
for event in parent['events']:
yield reset(event)
ioloop.IOLoop.current().run_sync(reset)
# Start checkers
def itercheckers(parent):
index = 0
for event in parent.get('events', []):
if 'checker' in event:
checker = event['checker']
p, m = checker['class'].rsplit('.', 1)
ioloop.IOLoop.current().spawn_callback(
getattr(importlib.import_module(p), m)(
event=event,
frequency=checker.get('frequency', 1),
params=checker['params']
).run)
index += 1
itercheckers(event)
itercheckers(configuration)
# Start alerts
ioloop.IOLoop.current().run_sync(alerts.reset)
for alert in configuration['alerts']:
p, m = alert['class'].rsplit('.', 1)
ioloop.IOLoop.current().spawn_callback(
getattr(importlib.import_module(p), m)(
alert=alert
).run
)
# Start loop
ioloop.IOLoop.current().start()
First thing first, self keyword is pointed to current websocket client that is handled in the moment. To use tornado websockets you must initialize tornado app
app = web.Application([
(r'/ws', WSHandler), #tells it to redirect ws:// to websocket handler
#Choose different names from defaults because of clarity
])
if __name__ == '__main__':
app.listen(5000) #listen on what port
ioloop.IOLoop.instance().start()
Then you must have WSHandler class you're pointing websockets trafic to
class WSHandler(websocket.WebSocketHandler):
#crossdomain connections allowed
def check_origin(self, origin):
return True
#when websocket connection is opened
def open(self):
print("Client connected ")
def on_close(self):
print("Client disconnected")
def on_message(self,message):
self.write_message(message) #echo back whatever client sent you
So complete app would look like
from tornado import websocket, web, ioloop
clients = []
#whenever you want to broadcast to all connected call this function
def broadcast_message(msg):
global clients
for client in clients:
client.write_message(msg)
class WSHandler(websocket.WebSocketHandler):
#crossdomain connections allowed
def check_origin(self, origin):
return True
#when websocket connection is opened
def open(self):
#here you can add clients to your client list if you want
clients.append(self)
print("Client connected ")
def on_close(self):
clients.remove(self)
print("Client disconnected")
def on_message(self,message):
self.write_message(message) #echo back whatever client sent you
app = web.Application([
(r'/ws', WSHandler), #tells it to redirect ws:// to websocket handler
#Choose different names from defaults because of clarity
])
if __name__ == '__main__':
app.listen(5000) #listen on what port
ioloop.IOLoop.instance().start()
And now to connect to it with js
function WebSocketTest()
{
var ws = new WebSocket("ws://localhost:5000/ws");
ws.onopen = function()
{
ws.send("initial connect")
}
ws.onmessage = function (evt)
{
console.log(evt.data)
};
ws.onclose = function()
{
console.log("closed ");
};
}
I haven't tested it but should work
I am trying to build a WebSocket session using Python 3.4, Django, Autobahn and JS. I have successfully run the websocket server on the python side, but i cannot subscribe or receive any data published by the server
My code is fairly simple:
class TestAppWS(ApplicationSession):
"""
An application component that publishes an event every second.
"""
def onConnect(self):
self.join(u"realm1")
#asyncio.coroutine
def onJoin(self, details):
counter = 0
while True:
self.publish('com.myapp.topic1', counter)
counter += 1
yield from asyncio.sleep(1)
def start_ws():
print("Running")
session_factory = ApplicationSessionFactory()
session_factory.session = TestAppWS
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# factory = WebSocketServerFactory("ws://localhost:8090", debug=False)
# factory.protocol = MyServerProtocol
server = None
try:
transport_factory = WampWebSocketServerFactory(session_factory, debug_wamp=True)
loop = asyncio.get_event_loop()
coro = loop.create_server(transport_factory, 'localhost', 8090)
server = loop.run_until_complete(coro)
loop.run_forever()
except OSError:
print("WS server already running")
except KeyboardInterrupt:
pass
finally:
if server:
server.close()
loop.close()
start_ws() is run inside a separate Thread object. If I access localhost:8090 on my browser I can see the Autobahn welcome message.
On the frontend I have
var connection = new autobahn.Connection({
url: 'ws://localhost:8090/',
realm: 'realm1'}
);
connection.onopen = function (session) {
var received = 0;
function onevent1(args) {
console.log("Got event:", args[0]);
received += 1;
if (received > 5) {
console.log("Closing ..");
connection.close();
}
}
session.subscribe('com.myapp.topic1', onevent1);
};
connection.open();
It does not seem to work, when I try to connect the frontend I get the following error on the backend side:
Failing WAMP-over-WebSocket transport: code = 1002, reason = 'WAMP Protocol Error (Received <class 'autobahn.wamp.message.Hello'> message, and session is not yet established)'
WAMP-over-WebSocket transport lost: wasClean = False, code = 1006, reason = 'connection was closed uncleanly (I failed the WebSocket connection by dropping the TCP connection)'
TX WAMP HELLO Message (realm = realm1, roles = [<autobahn.wamp.role.RolePublisherFeatures object at 0x04710270>, <autobahn.wamp.role.RoleSubscriberFeatures object at 0x047102B0>, <autobahn.wamp.role.RoleCallerFeatures object at 0x047102D0>, <autobahn.wamp.role.RoleCalleeFeatures object at 0x047102F0>], authmethods = None, authid = None)
RX WAMP HELLO Message (realm = realm1, roles = [<autobahn.wamp.role.RoleSubscriberFeatures object at 0x04710350>, <autobahn.wamp.role.RoleCallerFeatures object at 0x04710330>, <autobahn.wamp.role.RoleCalleeFeatures object at 0x04710390>, <autobahn.wamp.role.RolePublisherFeatures object at 0x04710370>], authmethods = None, authid = None)
Traceback (most recent call last):
File "C:\Python34\lib\site-packages\autobahn\wamp\websocket.py", line 91, in onMessage
self._session.onMessage(msg)
File "C:\Python34\lib\site-packages\autobahn\wamp\protocol.py", line 429, in onMessage
raise ProtocolError("Received {0} message, and session is not yet established".format(msg.__class__))
autobahn.wamp.exception.ProtocolError: Received <class 'autobahn.wamp.message.Hello'> message, and session is not yet established
on the javascript console I see:
Uncaught InvalidAccessError: Failed to execute 'close' on 'WebSocket': The code must be either 1000, or between 3000 and 4999. 1002 is neither.
Any idea? It looks like the session is not started, honestly it is not clear how this session work. Should not the session be initialized once a connection from the client is made?
Your TestAppWs and your browser code are both WAMP application components. Both of these need to connect to a WAMP router. Then they can talk freely to each other (as if there were no router in between .. transparently).
Here is how to run.
Run a WAMP Router.
Using Crossbar.io (but you can use other WAMP routers as well), that's trivial. First install Crossbar.io:
pip install crossbar
Crossbar.io (currently) runs on Python 2, but that's irrelevant as your app components can run on Python 3 or any other WAMP supported language/run-time. Think of Crossbar.io like a black-box, an external infrastructure, like a database system.
Then create and start a Crossbar.io default router:
cd $HOME
mkdir mynode
cd mynode
crossbar init
crossbar start
Run your Python 3 / asyncio component
import asyncio
from autobahn.asyncio.wamp import ApplicationSession
class MyComponent(ApplicationSession):
#asyncio.coroutine
def onJoin(self, details):
print("session ready")
counter = 0
while True:
self.publish('com.myapp.topic1', counter)
counter += 1
yield from asyncio.sleep(1)
if __name__ == '__main__':
from autobahn.asyncio.wamp import ApplicationRunner
runner = ApplicationRunner(url = "ws://localhost:8080/ws", realm = "realm1")
runner.run(MyComponent)
Run your browser component
var connection = new autobahn.Connection({
url: 'ws://localhost:8080/ws',
realm: 'realm1'}
);
connection.onopen = function (session) {
var received = 0;
function onevent1(args) {
console.log("Got event:", args[0]);
received += 1;
if (received > 5) {
console.log("Closing ..");
connection.close();
}
}
session.subscribe('com.myapp.topic1', onevent1);
};
connection.open();
I'm building a Python web server and need it to render HTML elements. As of right now the program is implemented via aj#ubuntu: ./server.py --base=home/website/html/ --port=8080 and then in the web browser, connecting to the URL http://localhost:8080/index.html. The only thing that displays on that web page however is HTTP/1.1 200 OK which is good since it means I am able to connect to the server. All I need now is the browser to be able to render the contents of the HTML, CSS, and corresponding Javascript components. Any suggestions? I've been scratching my brain trying to figure out how to do this.
I have included my code below:
import socket
import sys
if not sys.version_info[:2] == (3,4):
print("Error: need Python 3.4 to run program")
sys.exit(1)
else:
print("Using Python 3.4 to run program")
import argparse
def main():
parser = argparse.ArgumentParser(description='Project 1 Web Server for COMP/ECPE 177')
parser.add_argument('--version', help='Show program\'s version number and exit')
parser.add_argument('--base', action='store', help='Base directory containing website', metavar='/path/to/directory')
parser.add_argument('--port', action='store', type=int, help='Port number to listen on', metavar='####')
args = parser.parse_args()
#parser.print_help()
#print(args.port, args.base)
# Create TCP socket
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
except socket.error as msg:
print("Error: could not create socket")
print("Description: " + str(msg))
sys.exit()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Bind to listening port
try:
host='' # Bind to all interfaces
s.bind((host,args.port))
except socket.error as msg:
print("Error: unable to bind on port %d" % args.port)
print("Description: " + str(msg))
sys.exit()
# Listen
try:
backlog=10 # Number of incoming connections that can wait
# to be accept()'ed before being turned away
s.listen(backlog)
except socket.error as msg:
print("Error: unable to listen()")
print("Description: " + str(msg))
sys.exit()
print("Listening socket bound to port %d" % args.port)
while 1:
# Accept an incoming request
try:
(client_s, client_addr) = s.accept()
# If successful, we now have TWO sockets
# (1) The original listening socket, still active
# (2) The new socket connected to the client
except socket.error as msg:
print("Error: unable to accept()")
print("Description: " + str(msg))
sys.exit()
print("Accepted incoming connection from client")
print("Client IP, Port = %s" % str(client_addr))
# Receive data
try:
buffer_size=4096
raw_bytes = client_s.recv(buffer_size)
except socket.error as msg:
print("Error: unable to recv()")
print("Description: " + str(msg))
sys.exit()
string_unicode = raw_bytes.decode('ascii')
print("Received %d bytes from client" % len(raw_bytes))
print("Message contents: %s" % string_unicode)
request = str.split(string_unicode)
#print(request)
hcmd = request[0]
filep = request[1]
protocol = request[2]
print(filep)
if filep == '/':
filep = '/index.html'
if hcmd == 'GET':
dest_file = None
try:
try:
dest_file = open(args.base + filep, 'rb')
except (OSError, IOError) as msg:
msg = 'HTTP/1.1 404 Request Not Found\r\n\r\n'
statcode = 1 #404 request not found
rb1 = bytes(msg, 'ascii')
client_s.sendall(rb1)
message_send = 'HTTP/1.1 200 OK\r\n\r\n'
statcode = 0 #200 OK
rb2 = bytes(message_send, 'ascii')
client_s.sendall(rb2)
if dest_file is not None:
datasend = dest_file.read()
client_s.sendall(datasend)
dest_file.close()
print(dest_file)
print(statcode)
except socket.error as msg:
msg2 = "Error: "
sys.exit()
else:
message_send = 'HTTP/1.1 501 Not Implemented\r\n\r\n'
statuscode = 2 #501 not implemented
rb3 = bytes(message_send, 'ascii')
client_s.sendall(rb3)
client_s.close()
#Close both sockets
try:
s.close()
except socket.error as msg:
print("Error: unable to close() socket")
print("Description: " + str(msg))
sys.exit()
print("Sockets closed, now exiting")
if __name__ == "__main__":
sys.exit(main())
Does "home/website/html/index.html" exist in your server? I tried your codes. When accessing an existing file, it works fine. But for serving non-existing files, there's a bug in your codes. The codes for sending file content should be inside the inner-most 'try' block:
try:
dest_file = open(args.base + filep, 'rb')
#---------Should be put here-----------
message_send = 'HTTP/1.1 200 OK\r\n\r\n'
statcode = 0 #200 OK
rb2 = bytes(message_send, 'ascii')
client_s.sendall(rb2)
if dest_file is not None:
datasend = dest_file.read()
client_s.sendall(datasend)
dest_file.close()
print(dest_file)
print(statcode)
#--------------------------------------
except (OSError, IOError) as msg:
msg = 'HTTP/1.1 404 Request Not Found\r\n\r\n'
statcode = 1 #404 request not found
rb1 = bytes(msg, 'ascii')
client_s.sendall(rb1)
Besides, if it's not for learning purpose, using socket to build a web server is really too low level. You have so much extra work to do, e.g. manually compose the common http headers. Try some higher level libraries/frameworks, e.g.
Built-in http server: https://docs.python.org/3.4/library/http.server.html
The popular web framework Django: https://docs.djangoproject.com/en/dev/intro/
I am trying to write a realtime web app which can respond real time messages updating in Flask framework. I am using code from http://flask.pocoo.org/snippets/116/ but JavaScript EventSource SSE not firing in browser. From the log I can see I've published the data successfully but the webpage(http:xxxx:5000/) does not get updated at all.
import gevent
from gevent.wsgi import WSGIServer
from gevent.queue import Queue
from flask import Flask, Response
import time
# SSE "protocol" is described here: http://mzl.la/UPFyxY
class ServerSentEvent(object):
def __init__(self, data):
self.data = data
self.event = None
self.id = None
self.desc_map = {
self.data : "data",
self.event : "event",
self.id : "id"
}
def encode(self):
if not self.data:
return ""
lines = ["%s: %s" % (v, k)
for k, v in self.desc_map.iteritems() if k]
return "%s\n\n" % "\n".join(lines)
app = Flask(__name__)
subscriptions = []
# Client code consumes like this.
#app.route("/")
def index():
debug_template = """
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<h1>Server sent events</h1>
<div id="event"></div>
<script type="text/javascript">
var eventOutputContainer = document.getElementById("event");
var evtSrc = new EventSource("/subscribe");
evtSrc.onmessage = function(e) {
console.log(e.data);
eventOutputContainer.innerHTML = e.data;
};
</script>
</body>
</html>
"""
return(debug_template)
#app.route("/debug")
def debug():
return "Currently %d subscriptions" % len(subscriptions)
#app.route("/publish")
def publish():
#Dummy data - pick up from request for real data
def notify():
msg = str(time.time())
for sub in subscriptions[:]:
sub.put(msg)
print 'data is ' + str(time.time())
gevent.spawn(notify)
return "OK"
#app.route("/subscribe")
def subscribe():
def gen():
q = Queue()
subscriptions.append(q)
try:
while True:
result = q.get()
ev = ServerSentEvent(str(result))
print 'str(result) is: ' + str(result)
yield ev.encode()
except GeneratorExit: # Or maybe use flask signals
subscriptions.remove(q)
return Response(gen(), mimetype="text/event-stream")
if __name__ == "__main__":
app.debug = True
server = WSGIServer(("", 5001), app)
server.serve_forever()
# Then visit http://localhost:5000 to subscribe
# and send messages by visiting http://localhost:5000/publish
Can you please shed some lights? I am testing with Chrome/34.0.1847.116. Thanks.