I'm using Tornado Web Framework to build a websocket server and I need to connect via WebSocket from javascript. How can I get the server url and port from tornado templates?
I'm thinking in something like this:
<script>
var _url = "{{ (server_url) }}";
var _port = "{{ (server_port) }}";
var ws = new WebSocket("ws://" + _url + ":" + _port + "/socket");
</script>
I think you have to define it as context variables. Tornado doesn't provide this information to templates automatically.
It is a good idea to use tornado.options to set this variables. And then, pass them to your template.
Simplified contents of app.py:
from tornado.options import options, define
define("host", default="localhost", help="app host", type=str)
define("port", default=3000, help="app port", type=int)
class WebsocketHandler(tornado.web.RequestHandler):
def get(self):
self.render(
"index_websocket.html",
server_url=options.host,
server_port=options.port
)
options.parse_command_line() # don't forget to parse command line
app = tornado.web.Application(...)
app.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
In that case, you can run you application and provide defined settings:
python app.py --host=yourserveraddres.com --port=3000
Related
I'm currently following a Udemy course that's having me use flask and socketio to use a neural network model to drive a simulated car. However, as he's explaining the basics of how flask and socketio work, he had us write this code:
import socketio
import eventlet
from flask import Flask
sio = socketio.Server()
app = Flask(__name__)
#sio.on('connect')
def connect(sid, environ):
print('Connected')
if __name__ == "__main__":
app = socketio.Middleware(sio, app)
eventlet.wsgi.server(eventlet.listen(('', 4567)), app)
Which is supposed to print "Connected!" to the console when we connect to the server. Now, I get this message when I run it, so I'm pretty sure I'm connected.
(7532) accepted ('127.0.0.1', 49374)
But it's refusing to print "Connected!" when I connect like it's supposed to, no matter what I try.
EDIT:
So, I'm still not sure what the root cause of this is, but I found out how to fix it.
conda install python-engineio==3.13.2
conda install python-socketio==4.6.1
You might need to run anaconda as an administrator. If so, search for "Anaconda Powershell Prompt" then run it as an admin.
socketio uses special protocol so you need special client to use it.
At least
import socketio
sio = socketio.Client()
con = sio.connect('http://0.0.0.0:4567')
sio.wait()
I will not work with web browser. With web browser you can see only accepted.
Browser have to load web page which uses special JavaScript module to use socketio.
You can find more details in socketio documentation: Client
EDIT:
And here server which you can test with web browser.
When you open http://0.0.0.0:4567 in browser then index() sends to browser HTML with JavaScript code which loads special library and uses socketio to send own event. And you should see Connected, my event. When you close page or browser then you should see Disconnected
It is based on example from documentation for flask-socketio
import socketio
import eventlet
from flask import Flask
sio = socketio.Server()
app = Flask(__name__)
#app.route('/')
def index():
return """
Hello World!
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js" integrity="sha512-q/dWJ3kcmjBLU4Qc47E4A9kTB4m3wuTY7vkFJDTZKjTs8jhyGQnaUrxa0Ytd0ssMZhbNua9hE+E7Qv1j+DyZwA==" crossorigin="anonymous"></script>
<script type="text/javascript" charset="utf-8">
var socket = io();
socket.on('connect', function() {
socket.emit('my event', {data: 'Im connected!'});
});
</script>
"""
#sio.on('connect')
def connect(sid, environ):
print('Connected')
#sio.on('disconnect')
def disconnect(sid): # without `environ`
print('Disconnected')
#sio.on('my event')
def my_event(sid, environ):
print('my event')
if __name__ == "__main__":
app = socketio.Middleware(sio, app)
eventlet.wsgi.server(eventlet.listen(('', 4567)), app)
Let us create an application server and an admin server. Assume that fusionListener and adminListener contain the application and admin logic we want to expose.
from cherrypy._cpserver import Server
fserver = Server()
fserver.socket_port = 10000
fserver.subscribe()
aserver = Server()
aserver.socket_port = 10001
aserver.subscribe()
And then to start them:
cherrypy.engine.start()
cherrypy.engine.block()
The tree.mount parameters ask for:
the code/ business logic as the first parameter
listening url
config parameters
Here is how that looks for the above servers:
cherrypy.tree.mount(fusionListener, r"/fusion.*",fusionConf)
cherrypy.tree.mount(adminListener, r"/admin.*",adminConf)
But where is the parameter for the server itself - which includes the port being listened to?
This is not a well supported case for CherryPy.
The application selection (cherrypy.tree is basically a map of /path -> App) is done before the request dispatch and... long story short, you could use cherrypy.dispatch.VirtualHost and map you sub applications under a main one (that will route depending on the hostname (which the port can be part of). For the listening on multiple ports, can be done, but again this is a very custom arrangement.
I hope this example is illustrative of a possible way to make such feat:
import cherrypy
from cherrypy import dispatch
from cherrypy._cpserver import Server
class AppOne:
#cherrypy.expose
def default(self):
return "DEFAULT from app ONE!"
#cherrypy.expose
def foo(self):
return "FOO from app ONE"
class AppTwo:
#cherrypy.expose
def default(self):
return "DEFAULT from app TWO!"
#cherrypy.expose
def foo(self):
return "FOO from app TWO"
class Root:
def __init__(self):
self.one = AppOne()
self.two = AppTwo()
def bind_two_servers(app_one_port, app_two_port):
# unsubscribe the default server
cherrypy.server.unsubscribe()
s1 = Server()
s2 = Server()
s1.socket_port = app_one_port
s2.socket_port = app_two_port
# subscribe the server to the `cherrypy.engine` bus events
s1.subscribe()
s2.subscribe()
def start_server():
bind_two_servers(8081, 8082)
cherrypy.engine.signals.subscribe()
cherrypy.engine.start()
cherrypy.engine.block()
config = {
'/': {
'request.dispatch': dispatch.VirtualHost(**{
'localhost:8081': '/one',
'localhost:8082': '/two',
})
}
}
cherrypy.tree.mount(Root(), '/', config)
start_server()
This example will serve AppOne when coming from localhost:8081 and AppTwo when coming from localhost:8082.
The problem is that you can't do multiples cherrypy.tree.mount and expect to route into the different applications using the VirtualHost dispatcher, it assumes that the application resolution is done at that point and is only resolving the path of that application.
Having said all of that... I do not recommend this solution, it can get complicated and it would be better to have some other server in front (like nginx) and serve each path on different processes. This could be an alternative, only if you really really want to avoid any extra server or process in your setup.
I'm investigating the possibility of using a Flask application as an interface to an embedded system. I've used flask before (I've written some very basic flask sites to poll external systems in response to a page load to populate a chart for example) but I'm not sure how I would go about pushing data into the Flask application and on to the user's browser(s).
I was planning on pushing data from a C++ application running on the embedded device into the flask application (also running on the embedded device) using ZeroMQ.
From what I've read, something like flask-socketIO would be a possibility to get things from Flask to the user's browser.
The one thing that's not clear to me is whether it's possible / how you would go about receiving data from ZeroMQ and pushing that out to the browser?
In case anyone else wants to do the same, this is the simplest example I could boil things down to based on the example by reptilicus...
Instructions
Set the code below laid out in the structure mentioned below.
Install the Python modules listed below
Run the server
Run the data source
Open a web browser and navigate to http://localhost:25000/
If everything worked you should see a very basic page along these lines:
Python / Modules
These were the versions my test implementation used. Others may work too.
Python v3.5.2
Pyzmq v15.2.0
gevent v1.2.0
karellen-geventws v1.0.1 (required over gevent-websocket for Python 3 support)
Flask v0.10.1
Flask-Sockets v0.2.1
You also need a copy of Reconnecting Websocket, available here.
Code layout
\ZmqFlaskForwarder
\static
\js
application.js
reconnecting-websocket.min.js
\templates
index.html
data_source.py
server.py
Server App (server.py)
import zmq.green as zmq
import json
import gevent
from flask_sockets import Sockets
from flask import Flask, render_template
import logging
from gevent import monkey
monkey.patch_all()
app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
sockets = Sockets(app)
context = zmq.Context()
ZMQ_LISTENING_PORT = 12000
#app.route('/')
def index():
logger.info('Rendering index page')
return render_template('index.html')
#sockets.route('/zeromq')
def send_data(ws):
logger.info('Got a websocket connection, sending up data from zmq')
socket = context.socket(zmq.SUB)
socket.connect('tcp://localhost:{PORT}'.format(PORT=ZMQ_LISTENING_PORT))
socket.setsockopt_string(zmq.SUBSCRIBE, "")
poller = zmq.Poller()
poller.register(socket, zmq.POLLIN)
gevent.sleep()
received = 0
while True:
received += 1
# socks = dict(poller.poll())
# if socket in socks and socks[socket] == zmq.POLLIN:
data = socket.recv_json()
logger.info(str(received)+str(data))
ws.send(json.dumps(data))
gevent.sleep()
if __name__ == '__main__':
logger.info('Launching web server')
from gevent import pywsgi
from geventwebsocket.handler import WebSocketHandler
server = pywsgi.WSGIServer(('', 25000), app, handler_class=WebSocketHandler)
logger.info('Starting serving')
server.serve_forever()
Data Source (data_source.py)
import zmq
import random
import sys
import time
import json
port = "12000"
context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://*:%s" % port)
while True:
first_data_element = random.randrange(2,20)
second_data_element = random.randrange(0,360)
message = json.dumps({'First Data':first_data_element, 'Second Data':second_data_element})
print(message)
socket.send_string(message)
time.sleep(0.5)
Client JavaScript (application.js)
ws = new ReconnectingWebSocket("ws://" + location.host + '/zeromq')
ws.onmessage = function(message) {
payload = JSON.parse(message.data);
$('#latest_data').html('<h2> Data: ' + message.data + '</h2>');
};
Template (index.html)
<!DOCTYPE html>
<html>
<head>
<title>Python Websockets ZeroMQ demo</title>
</head>
<body>
<div class="container">
<h2> Simple ZeroMQ data streaming via web sockets! </h2>
<div id="latest_data"></div>
</div>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script type="text/javascript" src="static/js/reconnecting-websocket.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.6/d3.min.js"></script>
<script type="text/javascript" src="static/js/application.js"></script>
</body>
</html>
I have a web app Foo which is not secured, and I want to secure it using Flask, and more features will come also in this Flask app.
Foo is accessible via port 8081. Foo and this Flask web are in the same server.
Do I have to rewrite every end point in Foo as follows?
#app.route('/overview')
def foo_overview():
if check_token():
r = requests.get("http://localhost:8081/overview")
return r.content
else:
return redirect('/login')
or is there a convenient way to open a tunnel to localhost?
#app.route('/')
def foo_app():
if check_token():
# port 8081 now is opened for this connection
url = tunnel("http://localhost:8081")
return redirect(url)
else:
return redirect('/login')
My idea is to not open port 8081, but open 80, so users go from port 80 and if passing security check, they can start to use the service at 8081.
from flask import stream_with_context
#app.route('/<path:path>')
def foo_app(path):
if check_token():
# port 8081 now is opened for this connection
req = requests.get("http://localhost:8001/%s"%path, stream = True)
return Response(stream_with_context(req.iter_content()), content_type = req.headers['content-type'])
else:
return redirect('/login')
I borrowed most of this from http://flask.pocoo.org/snippets/118/
I'm trying to launch a webserver serving several sites under several subdomains.
I'm using Pythen with webapp2 and paste. My Server is behind a router that asigns a static IP adress to the server and forwards port 80. The router itself has no static IP adress assigned by my ISP so I'm using DDNS (lets say example.dlinkddns.com) .
In my folder hierarchy each folder represents a subdomain and is a python module.
Like this:
server/app.py
server/www
server/www/__init__.py
server/test
server/test/__init__.py
they should be reachable via www.mydomain.com and test.mydomain.com
I set *.mydomain.com to be a CNAME for example.dlinkddns.com
This is server/app.py:
import sys
import os
import webapp2
from webapp2_extras import routes
from paste import httpserver
DOMAIN = 'mydomain.com'
class Fallback(webapp2.RequestHandler):
def get(self, *args, **kw):
self.response.write('Fallback...\n'+str(args)+'\n'+str(kw))
def main():
dirs = [name for name in os.listdir(".") if os.path.isdir(name)]
dirs.remove('env') # folder created by package virtualenv - needed for paste
rs = []
for subdomain in dirs:
# import subdomain package
exec('import '+subdomain)
# add routes defined for subdomain
exec('rs += [routes.DomainRoute("'+subdomain+'.'+DOMAIN+'", '+subdomain+'.routes)]')
rs += [routes.DomainRoute("<domain:.*>",[webapp2.Route('/<:.*>',Fallback,name='fallback')])]
app = webapp2.WSGIApplication(rs)
httpserver.serve(app, host='0.0.0.0', port='80')
if __name__ == '__main__':
main()
And this is how my www package looks like:
__init__.py
import webapp2
class MainMain(webapp2.RequestHandler):
def get(self,target):
self.response.write('Hello, webapp2! www.mydomain.com at:'+target)
routes = [
webapp2.Route('/<target>', handler=MainMain, name='main-main')
]
The problem now is, when I visit www.mydomain.com the fallback handler seems to match but MainMain handler should.
The output is
Fallback... () {'domain':'0.0.0.0'}.
It looks like my app wouldn't recognize the domains at all. Same thing happens when I visit example.dlinkddns.com.
I also tried it without the fallback handler but then it only showed me the 404 page for every route and every domain...
I also tried to start the server with
httpserver.serve(app, host='192.168.178.33', port='80')
This is the static assigned IP address of my server in my LAN. The the output changes to
Fallback... () {'domain':'192.168.178.33'}.
What am I doing wrong? Thanks for any help!