Phusion Passenger needs to launch the webserver using its own function, so a custom function to launch a webserver cannot be used, while SocketIO seems to require to use one. Is there is any way to bypass this?
Sample code:
from flask import Flask
from flask_socketio import SocketIO
app = Flask('')
socket = SocketIO(app)
@app.route('/')
def home():
return open("index.html", "r").read()
@socket.on('echo')
def echo(json):
return json
# Phusion Passenger already does this, so we shouldnt run that next line or else we will never run the program
#app.run(host='0.0.0.0',port=80)
After hours of work on this, I finally got it working. The problem is that Passenger uses parallelism for web requests, which Flask-SocketIO's session system do not support due to storage of data being messy when parallelized. However, creating our own session system and injecting it to Flask-SocketIO makes it work. I am using Redis for storing sessions, but a MySQL or multithreaded SQLite could also work with some adaptations:
from flask import Flask
from flask_socketio import SocketIO
app = Flask('')
#socketio = SocketIO(app) #redefined later
# special config for passenger
from flask import g
import json, redis, uuid
socketio = SocketIO(app, transports=['polling'], async_mode="threading")
SESSION_KEY_PREFIX = "yourprojectname_session:"
redis_socket_path = "sock.sock"
redis_password = "password"
redis_store = redis.StrictRedis(
unix_socket_path=redis_socket_path,
password=redis_password,
decode_responses=True
)
redis_store.ping()
class CustomSession:
def __init__(self, sid=None):
self.sid = sid or str(uuid.uuid4())
self.data = {}
def get(self, key, default=None):
return self.data.get(key, default)
def set(self, key, value):
self.data[key] = value
redis_store.set(f"{SESSION_KEY_PREFIX}{self.sid}", self.data)
def load(self):
data = redis_store.get(f"{SESSION_KEY_PREFIX}{self.sid}")
if data:
self.data = json.loads(data)
def save(self):
redis_store.setex(
f"{SESSION_KEY_PREFIX}{self.sid}",
86400 * 9999, # 9999 days expiry
json.dumps(self.data)
)
@socketio.on('connect')
def handle_connect():
session_id = session.get('sid')
if not session_id:
session_id = str(uuid.uuid4())
session['sid'] = session_id
custom_session = CustomSession(session_id)
custom_session.load()
g.custom_session = custom_session
@socketio.on('disconnect')
def handle_disconnect():
custom_session = getattr(g, 'custom_session', None)
if custom_session:
custom_session.save()
# end of the phusion passenger patch/config
@app.route('/')
def home():
return open("index.html", "r").read()
@socketio.on('echo')
def echo(json):
return json
# Phusion Passenger already does this, so we shouldnt run that next line or else we will never run the program
#app.run(host='0.0.0.0',port=80)
I also noticed that since Phusion Passenger relies on other software like Apache or Nginx, it is very unlikely to support websocket, so I recommend (as I did) to use long polling by default, and use the "threading" mode to make sure Flask-SocketIO is aware it will run in parallelized.
Feel free to use my code under no license at all.