I am using python socketio for communication and it works well for http. Have a problem when upgraded it to work with SSL.
I made a self-signed root certificate (CA), and issued server.cert and server.key. I told the computer to trust the CA. After that, I added the server.cert and server.key on the flask-socketio server side. And the code looks like this:
from flask import Flask, render_template
from flask_socketio import SocketIO
from flask_socketio import Namespace
app = Flask(__name__, template_folder="templates")
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
# Create a URL route in our application for "/"
#app.route('/')
def home():
"""
This function loads the homepage
"""
return render_template('index.html')
class MyCustomNamespace(Namespace):
def on_connect(self):
print("Client just connected")
def on_disconnect(self):
print("Client just left")
def on_messages(self, data):
print(f"\nReceived data from client: \n {data}\n")
return data
socketio.on_namespace(MyCustomNamespace('/channel_A'))
if __name__ == "__main__":
socketio.run(app, host="192.168.0.28", port=2000, certfile="server.crt", keyfile="server.key", server_side=True, debug=True)
#socketio.run(app, host="192.168.0.28", port=2000, debug=True)
The code for client connection is simply:
import socketio
sio = socketio.Client()
def message_received(data):
print(f"Message {data} received")
#sio.on('connect', namespace="/channel_A")
def on_connect():
print("Connect...")
#sio.on('disconnect', namespace="/channel_A")
def on_disconnect():
print(f"Disconnected from server")
if __name__ == '__main__':
sio.connect('https://192.168.0.28:2000', namespaces="/channel_A")
emit_times = 0
is_emit = True
data = '{"data": "foobar"}'
while is_emit:
sio.emit("messages", data, namespace="/channel_A", callback=message_received)
emit_times += 1
if emit_times > 1:
is_emit = False
sio.disconnect()
I am using python-socketio (https://python-socketio.readthedocs.io/en/latest/client.html#disconnecting-from-the-server) for client end.
When the server gets started, the website works well and the connection is secure. The command line looks like this:
* Restarting with stat
* Debugger is active!
* Debugger PIN: 142-782-563
(3484) wsgi starting up on https://192.168.0.28:2000
When SocketIO client tries to connect with the server, the connection is refused and on the server end, the error is finally thrown like this:
ssl.SSLError: [SSL: TLSV1_ALERT_UNKNOWN_CA] tlsv1 alert unknown ca (_ssl.c:2488)
I think I probably miss something. Any ideas? Thanks in advance!
It seems that this is a bug in a newer SocketIO version. You can try to downgrade to 2.x.x:
pip install python-socketio<3.0.0 --force-reinstall
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)
I want to debug small flask server inside jupyter notebook for demo.
I created virtualenv on latest Ubuntu and Python2 (on Mac with Python3 this error occurs as well), pip install flask jupyter.
However, when I create a cell with helloworld script it does not run inside notebook.
from flask import Flask
app = Flask(__name__)
#app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run(debug=True,port=1234)
File
"/home/***/test/local/lib/python2.7/site-packages/ipykernel/kernelapp.py",
line 177, in _bind_socket
s.bind("tcp://%s:%i" % (self.ip, port)) File "zmq/backend/cython/socket.pyx", line 495, in
zmq.backend.cython.socket.Socket.bind
(zmq/backend/cython/socket.c:5653) File
"zmq/backend/cython/checkrc.pxd", line 25, in
zmq.backend.cython.checkrc._check_rc
(zmq/backend/cython/socket.c:10014)
raise ZMQError(errno) ZMQError: Address already in use
NB - I change the port number after each time it fails.
Sure, it runs as a standalone script.
update without (debug=True) it's ok.
I installed Jupyter and Flask and your original code works.
The flask.Flask object is a WSGI application, not a server. Flask uses Werkzeug's development server as a WSGI server when you call python -m flask run in your shell. It creates a new WSGI server and then passes your app as paremeter to werkzeug.serving.run_simple. Maybe you can try doing that manually:
from werkzeug.wrappers import Request, Response
from flask import Flask
app = Flask(__name__)
#app.route("/")
def hello():
return "Hello World!"
if __name__ == '__main__':
from werkzeug.serving import run_simple
run_simple('localhost', 9000, app)
Flask.run() calls run_simple() internally, so there should be no difference here.
The trick is to run the Flask server in a separate thread. This code allows registering data providers. The key features are
Find a free port for the server. If you run multiple instances of the server in different notebooks they would compete for the same port.
The register_data function returns the URL of the server so you can use it for whatever you need.
The server is started on-demand (when the first data provider is registered)
Note: I added the #cross_origin() decorator from the flask-cors package. Else you cannot call the API form within the notebook.
Note: there is no way to stop the server in this code...
Note: The code uses typing and python 3.
Note: There is no good error handling at the moment
import socket
import threading
import uuid
from typing import Any, Callable, cast, Optional
from flask import Flask, abort, jsonify
from flask_cors import cross_origin
from werkzeug.serving import run_simple
app = Flask('DataServer')
#app.route('/data/<id>')
#cross_origin()
def data(id: str) -> Any:
func = _data.get(id)
if not func:
abort(400)
return jsonify(func())
_data = {}
_port: int = 0
def register_data(f: Callable[[], Any], id: Optional[str] = None) -> str:
"""Sets a callback for data and returns a URL"""
_start_sever()
id = id or str(uuid.uuid4())
_data[id] = f
return f'http://localhost:{_port}/data/{id}'
def _init_port() -> int:
"""Creates a random free port."""
# see https://stackoverflow.com/a/5089963/2297345
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('localhost', 0))
port = sock.getsockname()[1]
sock.close()
return cast(int, port)
def _start_sever() -> None:
"""Starts a flask server in the background."""
global _port
if _port:
return
_port = _init_port()
thread = threading.Thread(target=lambda: run_simple('localhost', _port, app))
thread.start()
Although this question was asked long ago, I come up with another suggestion:
The following code is adapted from how PyCharm starts a Flask console.
import sys
from flask.cli import ScriptInfo
app = None
locals().update(ScriptInfo(create_app=None).load_app().make_shell_context())
print("Python %s on %s\nApp: %s [%s]\nInstance: %s" % (sys.version, sys.platform, app.import_name, app.env, app.instance_path))
Now you can access app and use everything described in the Flask docs on working with the shell
I'm stuck in development of socket.io with Python. I'm using this lib
I got a chat app running by using this android part and with the lib sample. I want to trigger an event from the server side from a separate file. Here's my code.
import socketio
import eventlet
from flask import Flask, render_template
sio = socketio.Server(logger=True, async_handlers= True)
app = Flask(__name__)
eventlet.monkey_patch()
#sio.on('connect', namespace='/d')
def connect(sid, environ):
print('connect ', sid)
pass
#sio.on('messaget', namespace='/d')
def messaget(sid, data):
print('message ', data)
# sio.emit('messaget', data, namespace='/d')
# sendmsg("YO YO")
#sio.on('disconnect', namespace='/d')
def disconnect(sid):
print('disconnect ', sid)
def start_socket(app):
# wrap Flask application with socketio's middleware
app = socketio.Middleware(sio, app)
eventlet.wsgi.server(eventlet.listen(('', 8000)), app)
def sendmsg(data):
my_data= { 'text': data };
sio.emit('messaget', my_data, namespace='/d')
start_socket(app)
I'm calling sendmsg("dipen") from my another python file. I'm getting a log emitting event "messaget" to all [/d] but android app is not getting any messages. And it work's if event is emitting from the Android app. I tried with the NodeJs code and it worked for the NodeJs code so I'm pretty sure that something is wrong in my Python code. Hope that someone could save me from this.
Send a message from Android client. Does your Python code get it? If not, check if you have connected to the same namespace.
When I try to send a socket message outside from the SocketIO event context, the message does not arrive at the client.
Method outside of the context:
#main.route('/import/event', methods=['POST'])
def update_client():
from .. import socketio
userid = request.json['userid']
data = request.json
current_app.logger.info("New data: {}".format(data))
current_app.logger.info("Userid: {}".format(userid))
socketio.emit('import', data, namespace='/events', room=userid)
return 'ok'
I also tried:
with current_app.app_context():
socketio.emit('import', data, namespace='/events', room=userid)
On the SocketIO Context 'on.connect'
#socketio.on('connect', namespace='/events')
def events_connect():
current_app.logger.info("CONNECT {}".format(request.namespace))
userid = request.sid
current_app.clients[userid] = request.namespace
The method update_client will be called from a thread.
On the Client side:
$(document).ready(function(){
var socket = io.connect('http://' + document.domain + ':' + location.port +'/events');
var userid;
socket.on('connect', function() {
console.log("on connect");
});
socket.on('import', function (event) {
console.log("On import" +event);
});
When I call the emit('import', 'test') in the #socketio.on('connect') method the messages arrives at the client and the log message is printed.
There is an example in the documentation:
#app.route('/ping')
def ping():
socketio.emit('ping event', {'data': 42}, namespace='/chat')
Do I miss something or why does the message not arrive at the client?
Edit:
The socketio is created in the app/__init__.py function
socketio = SocketIO()
create_app():
app = Flask(__name__)
socketio.init_app(app)
manage.py
from app import socketio
if __name__ == '__main__':
socketio.run(app)
Flask-SocketIO==2.6
eventlet==0.19.0
I found a solution.
When I run the application with the flask internal server, the messages are not received by the client.
python manage.py run
But when I run the server with gunicorn all works like a charm.
So the solution here is to use the gunicorn with eventlet.
gunicorn --worker-class eventlet -w 1 manage:app
I use Flask 0.11 with Flask-Migrate 2.0. Perhaps I missed something, since Flask 0.11 has a new startup command.
I'm working on my first iOS app that uses push notifications. I have a python script that lets me to send push notifications from my machine but I'm unable to get this working with the Google App Engine Launcher.
When I run this on GAE I get nothing - no errors and no push notifications. What am I doing wrong? I know the code for sending the actual notification is working properly but I'm not able to duplicate this on Google's servers.
Here is the script I'm trying to run with GAE Launcher:
import os
import cgi
import webapp2
from google.appengine.ext.webapp.util import run_wsgi_app
import ssl
import json
import socket
import struct
import binascii
TOKEN = 'my_app_token'
PAYLOAD = {'aps': {'alert':'Push!','sound':'default'}}
class APNStest(webapp2.RequestHandler):
def send_push(token, payload):
# Your certificate file
cert = 'ck.pem'
# APNS development server
apns_address = ('gateway.sandbox.push.apple.com', 2195)
# Use a socket to connect to APNS over SSL
s = socket.socket()
sock = ssl.wrap_socket(s, ssl_version=ssl.PROTOCOL_SSLv3, certfile=cert)
sock.connect(apns_address)
# Generate a notification packet
token = binascii.unhexlify(token)
fmt = '!cH32sH{0:d}s'.format(len(payload))
cmd = '\x00'
message = struct.pack(fmt, cmd, len(token), token, len(payload), payload)
sock.write(message)
sock.close()
send_push(TOKEN, json.dumps(PAYLOAD))
application = webapp2.WSGIApplication([
('/apns', APNStest)
], debug=True)
def main():
run_wsgi_app(application)
if __name__ == "__main__":
main()
So the solution was very simple as I expected. I had enabled billing for the project on cloud.google.com but needed to have billing enabled at appengine.google.com as well. Stupid mistake that set me back 2 days.