Pour utiliser MQTT sur WebSockets en Python, créez le client paho avec transport="websockets", connectez-vous au port WebSocket du broker et, si besoin, définissez le chemin de l'URL avec ws_set_options(). Pour du wss chiffré, ajoutez tls_set() et utilisez le port WebSocket sécurisé. Pour le reste, l'API MQTT est identique : mêmes callbacks, mêmes publish() et subscribe().
Ce guide montre les connexions ws et wss, les chemins et en-têtes personnalisés, la connexion via des proxys HTTP et SOCKS, et comment déboguer les erreurs de handshake levées par paho.
Quand les WebSockets ont du sens en Python
Les navigateurs ne peuvent parler MQTT qu'en WebSockets, mais Python peut utiliser du TCP brut, plus simple et un peu plus léger. Choisissez les WebSockets en Python quand :
- Votre réseau n'autorise que le HTTP(S) sortant, par exemple un pare-feu d'entreprise ou le Wi-Fi d'un hôtel qui bloque le 1883 et le 8883 (voir MQTT derrière un pare-feu d'entreprise).
- Le broker se trouve derrière un reverse proxy ou un load balancer qui ne route que du HTTP, comme Nginx ou un ingress cloud.
- Vous voulez qu'un script backend utilise exactement le même point d'accès que votre tableau de bord web, pour reproduire un problème du navigateur.
Si rien de cela ne s'applique, TCP sur 1883/8883 reste le choix par défaut. L'écart de surcoût est mesuré dans performances TCP vs WebSocket.
WebSockets en clair (ws://)
import time
import paho.mqtt.client as mqtt
def on_connect(client, userdata, flags, reason_code, properties):
print("CONNACK:", reason_code)
client.subscribe("testmqtt/py-ws/#")
def on_message(client, userdata, msg):
print(msg.topic, msg.payload.decode())
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, transport="websockets")
client.ws_set_options(path="/mqtt") # /mqtt est aussi la valeur par défaut de paho
client.on_connect = on_connect
client.on_message = on_message
client.connect("broker.connectmq.com", 8083, keepalive=60)
client.loop_start()
client.publish("testmqtt/py-ws/hello", "hello over ws", qos=1)
time.sleep(2)
client.loop_stop()
client.disconnect()transport doit être défini dans le constructeur ; impossible de faire passer un client existant de TCP à WebSockets. Les valeurs valides sont "tcp", "websockets" et "unix" ; toute autre lève ValueError: transport must be "websockets", "tcp" or "unix", not ....
WebSockets sécurisés (wss://)
Au-delà d'un test rapide, utilisez wss. C'est le même code, plus tls_set(), le port sécurisé et de vrais identifiants :
import os
import paho.mqtt.client as mqtt
client = mqtt.Client(
mqtt.CallbackAPIVersion.VERSION2,
client_id="py-wss-demo",
transport="websockets",
protocol=mqtt.MQTTv5,
)
client.username_pw_set(os.environ["MQTT_USER"], os.environ["MQTT_PASS"])
client.tls_set() # vérifie le certificat et le nom d'hôte
client.ws_set_options(path="/mqtt")
client.on_connect = lambda c, u, f, rc, p: print("CONNACK:", rc)
client.connect("broker.connectmq.com", 8084, keepalive=60)
client.loop_forever()paho effectue d'abord le handshake TLS, puis l'upgrade HTTP dans le canal chiffré, puis le CONNECT MQTT. Toutes les options et erreurs TLS de MQTT sur TLS en Python s'appliquent telles quelles.
Chemins et en-têtes personnalisés
Le chemin WebSocket doit correspondre à ce qu'attend le broker ou le proxy, et il n'existe pas de standard universel. /mqtt est le plus courant (valeur par défaut d'EMQX, de HiveMQ et de nombreux brokers managés), le plugin Web MQTT de RabbitMQ utilise /ws, et les reverse proxys peuvent mapper le chemin de votre choix. Un mauvais chemin est la première cause d'échec de connexion WebSocket ; voir chemin WebSocket 404.
ws_set_options() accepte aussi des en-têtes, soit sous forme de dict fusionné avec les valeurs par défaut, soit sous forme de fonction qui reçoit les en-têtes par défaut et renvoie l'ensemble final :
import os
TOKEN = os.environ["GATEWAY_TOKEN"]
def add_headers(headers):
headers["Authorization"] = f"Bearer {TOKEN}" # pour un proxy d'authentification devant le broker
headers["Origin"] = "https://dashboard.example.com"
return headers
client.ws_set_options(path="/mqtt", headers=add_headers)Par défaut, paho envoie Origin: http://host:port (ou https:// avec TLS) et Sec-WebSocket-Protocol: mqtt. Remplacer Origin compte quand un proxy applique une liste d'origines autorisées, sujet traité dans CORS et contrôle d'origine WebSocket. Rappelez-vous que les identifiants MQTT passent toujours dans le paquet CONNECT via username_pw_set() ; les en-têtes ne servent qu'aux composants HTTP placés devant le broker.
Se connecter via un proxy HTTP ou SOCKS
paho traverse les proxys grâce à la bibliothèque PySocks. Installez-la d'abord avec pip install pysocks, puis configurez le proxy explicitement :
import socks # du paquet PySocks
import paho.mqtt.client as mqtt
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, transport="websockets")
client.tls_set()
client.proxy_set(proxy_type=socks.HTTP, proxy_addr="proxy.corp.local", proxy_port=3128)
client.connect("broker.connectmq.com", 8084)
client.loop_forever()Avec socks.HTTP, PySocks ouvre un tunnel HTTP CONNECT vers broker.connectmq.com:8084, et paho y fait passer TLS, l'upgrade WebSocket et MQTT. Utilisez socks.SOCKS5 (éventuellement avec proxy_username / proxy_password) pour un proxy SOCKS.
Deux détails font gagner beaucoup de temps :
- paho ignore
HTTPS_PROXYetHTTP_PROXY. La seule variable d'environnement qu'il lit estmqtt_proxy, par exempleexport mqtt_proxy=http://proxy.corp.local:3128ousocks://host:1080, et seulement si PySocks est installé. Les hôtes listés dansno_proxysont contactés directement. - Beaucoup de proxys d'entreprise n'autorisent
CONNECTque vers le port 443. Si le tunnel est refusé, il vous faut un point d'accès du broker sur le 443 ou une exception sur le proxy.
Erreurs de handshake et corrections
Les problèmes WebSocket apparaissent sous forme de WebsocketConnectionError, une sous-classe de ConnectionError. Avec connect(), elle est levée immédiatement ; avec connect_async() et loop_start(), paho réessaie en silence : activez client.enable_logger() pendant le débogage.
| Erreur | Cause probable | Correction |
|---|---|---|
WebSocket handshake error, connection not upgraded | Réponse HTTP sans upgrade : mauvais chemin (404), serveur web ordinaire, page d'erreur du proxy | Vérifiez le chemin ; assurez-vous que l'hôte et le port pointent vers le listener WebSocket |
WebSocket handshake error | Connexion fermée pendant l'upgrade ou réponse non HTTP (souvent du ws en clair vers un port TLS) | Utilisez tls_set() avec les ports wss ; vérifiez que le port est un listener WebSocket |
WebSocket handshake error, invalid secret key | Un proxy ou serveur a renvoyé un Sec-WebSocket-Accept incorrect | Contournez ou corrigez le proxy qui intercepte |
[SSL: WRONG_VERSION_NUMBER] wrong version number | tls_set() vers un port ws en clair comme le 8083 | Utilisez le 8084 pour wss, ou retirez tls_set() |
ConnectionRefusedError: [Errno 61] Connection refused (111 sous Linux) | Rien n'écoute sur ce port, ou un pare-feu le rejette | Vérifiez le port ; testez depuis un autre réseau |
CONNACK Not authorized / Bad user name or password | Le WebSocket fonctionne, l'authentification MQTT a échoué | Vérifiez identifiants et ACL |
Retrouvez les reason codes MQTT de la dernière ligne avec l'outil de recherche de reason codes.
Tester la connexion
Commencez par vérifier l'upgrade HTTP sans Python. Un point d'accès WebSocket sain répond 101 Switching Protocols :
curl -i -N --max-time 5 \
-H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
-H "Sec-WebSocket-Protocol: mqtt" \
https://broker.connectmq.com:8084/mqttUn 404 signifie un mauvais chemin, un 400 ou 426 indique généralement un en-tête supprimé, et une erreur TLS signifie que vous parlez au mauvais port. Faites ensuite un test de bout en bout : ouvrez le client MQTT en ligne, connectez-vous en wss au même broker, abonnez-vous à testmqtt/py-ws/# et lancez ws_client.py. Si le navigateur reçoit votre message, Python, le chemin et le broker sont corrects. Tester une connexion MQTT WebSocket propose d'autres vérifications, y compris pour les proxys et load balancers.
S'abonner dans le navigateur, publier depuis Python
Utiliser aiomqtt à la place
Si votre code est basé sur asyncio, aiomqtt (qui encapsule paho) expose les mêmes options comme arguments du constructeur :
import asyncio
import ssl
import aiomqtt
async def main():
async with aiomqtt.Client(
"broker.connectmq.com",
8084,
transport="websockets",
websocket_path="/mqtt",
tls_context=ssl.create_default_context(),
) as client:
await client.subscribe("testmqtt/py-ws/#")
await client.publish("testmqtt/py-ws/hello", "hello from aiomqtt")
async for message in client.messages:
print(message.topic, message.payload)
break
asyncio.run(main())MQTT sur WebSockets en Python, c'est un argument de constructeur plus le bon port et le bon chemin. Utilisez wss avec la vérification active, définissez le chemin explicitement pour que personne n'ait à le deviner, configurez les proxys avec proxy_set() ou mqtt_proxy, et testez l'upgrade avec curl avant d'accuser votre code.
Questions fréquentes
Quel port et quel chemin paho-mqtt utilise-t-il pour les WebSockets ?
paho utilise le port passé à connect() et, par défaut, le chemin /mqtt. Les WebSockets en clair sont souvent sur le 8083 ou le 80, les WebSockets sécurisés sur le 8084 ou le 443. Changez le chemin avec client.ws_set_options(path=...) si votre broker ou proxy en utilise un autre.
paho-mqtt tient-il compte de HTTPS_PROXY ou HTTP_PROXY ?
Non. paho ne lit qu'une variable d'environnement nommée mqtt_proxy (avec une URL http:// ou socks://), et seulement si PySocks est installé. Sinon, configurez le proxy explicitement avec client.proxy_set(proxy_type=socks.HTTP, proxy_addr=..., proxy_port=...).
Que signifie WebSocket handshake error, connection not upgraded ?
Le serveur a répondu à la requête HTTP d'upgrade sans passer en WebSocket. Le plus souvent, le chemin est faux (une page 404), vous avez atteint un serveur web ou un proxy ordinaire au lieu du listener WebSocket du broker, ou un proxy a supprimé l'en-tête Upgrade.


