Para usar MQTT sobre WebSockets em Python, crie o cliente paho com transport="websockets", conecte na porta WebSocket do broker e, se necessário, defina o path da URL com ws_set_options(). Para wss criptografado, adicione tls_set() e use a porta de WebSocket seguro. Fora isso, a API MQTT é idêntica: os mesmos callbacks, o mesmo publish() e o mesmo subscribe().
Este guia mostra conexões ws e wss, paths e headers personalizados, conexão por proxies HTTP e SOCKS e como depurar os erros de handshake que o paho lança.
Quando WebSockets fazem sentido no Python
Navegadores só falam MQTT via WebSockets, mas o Python pode usar TCP puro, que é mais simples e um pouco mais leve. Escolha WebSockets no Python quando:
- A sua rede só permite HTTP(S) de saída, por exemplo um firewall corporativo ou a rede de um hotel que bloqueia 1883 e 8883 (veja MQTT atrás de um firewall corporativo).
- O broker fica atrás de um reverse proxy ou load balancer que só roteia HTTP, como Nginx ou um ingress na nuvem.
- Você quer que um script de backend use exatamente o mesmo endpoint do seu dashboard web, para reproduzir um problema do navegador.
Se nada disso se aplica, TCP em 1883/8883 é a escolha padrão. A diferença de overhead está medida em desempenho TCP vs WebSocket.
WebSockets sem criptografia (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 também é o padrão do 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()O transport precisa ser definido no construtor; não dá para trocar um cliente existente de TCP para WebSockets. Os valores válidos são "tcp", "websockets" e "unix"; qualquer outro lança ValueError: transport must be "websockets", "tcp" or "unix", not ....
WebSockets seguros (wss://)
Para qualquer coisa além de um teste rápido, use wss. É o mesmo código mais tls_set(), a porta segura e credenciais reais:
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() # verifica o certificado e o hostname
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()O paho faz primeiro o handshake TLS, depois o upgrade HTTP dentro do canal criptografado e então o CONNECT do MQTT. Todas as opções e erros de TLS de MQTT sobre TLS em Python valem sem mudanças.
Paths e headers personalizados
O path do WebSocket precisa bater com o que o broker ou o proxy espera, e não existe padrão universal. /mqtt é o mais comum (é o padrão no EMQX, no HiveMQ e em muitos brokers gerenciados), o plugin Web MQTT do RabbitMQ usa /ws, e reverse proxies podem mapear qualquer path que você escolher. Path errado é a causa número um de falhas de conexão WebSocket; veja WebSocket path 404.
O ws_set_options() também aceita headers, seja como um dict mesclado aos padrões, seja como uma função que recebe os headers padrão e retorna o conjunto final:
import os
TOKEN = os.environ["GATEWAY_TOKEN"]
def add_headers(headers):
headers["Authorization"] = f"Bearer {TOKEN}" # para um proxy de autenticação na frente do broker
headers["Origin"] = "https://dashboard.example.com"
return headers
client.ws_set_options(path="/mqtt", headers=add_headers)Por padrão o paho envia Origin: http://host:port (ou https:// com TLS) e Sec-WebSocket-Protocol: mqtt. Sobrescrever o Origin importa quando um proxy aplica uma lista de origens permitidas, assunto de CORS e verificação de origem no WebSocket. Lembre que as credenciais MQTT continuam indo no pacote CONNECT via username_pw_set(); headers servem apenas para componentes HTTP na frente do broker.
Conectando por um proxy HTTP ou SOCKS
O paho atravessa proxies com a biblioteca PySocks. Instale primeiro com pip install pysocks e configure o proxy explicitamente:
import socks # do pacote 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()Com socks.HTTP, o PySocks abre um túnel HTTP CONNECT até broker.connectmq.com:8084, e o paho roda TLS, o upgrade WebSocket e o MQTT por dentro dele. Use socks.SOCKS5 (opcionalmente com proxy_username / proxy_password) para um proxy SOCKS.
Dois detalhes economizam muito tempo:
- O paho ignora
HTTPS_PROXYeHTTP_PROXY. A única variável de ambiente que ele lê émqtt_proxy, por exemploexport mqtt_proxy=http://proxy.corp.local:3128ousocks://host:1080, e só se o PySocks estiver instalado. Hosts emno_proxysão ignorados pelo proxy. - Muitos proxies corporativos só permitem
CONNECTpara a porta 443. Se o túnel for recusado, você precisa de um endpoint do broker na 443 ou de uma exceção no proxy.
Erros de handshake e como corrigi-los
Problemas de WebSocket aparecem como WebsocketConnectionError, uma subclasse de ConnectionError. Com connect() ele é lançado na hora; com connect_async() mais loop_start() o paho tenta de novo em silêncio, então ative client.enable_logger() enquanto depura.
| Erro | Causa provável | Correção |
|---|---|---|
WebSocket handshake error, connection not upgraded | Resposta HTTP sem upgrade: path errado (404), servidor web comum, página de erro do proxy | Confira o path; garanta que host e porta apontam para o listener WebSocket |
WebSocket handshake error | Conexão fechada durante o upgrade ou resposta não HTTP (muitas vezes ws sem criptografia contra uma porta TLS) | Use tls_set() com portas wss; confira se a porta é um listener WebSocket |
WebSocket handshake error, invalid secret key | Um proxy ou servidor devolveu um Sec-WebSocket-Accept errado | Contorne ou corrija o proxy que intercepta |
[SSL: WRONG_VERSION_NUMBER] wrong version number | tls_set() contra uma porta ws sem criptografia, como a 8083 | Use a 8084 para wss, ou remova o tls_set() |
ConnectionRefusedError: [Errno 61] Connection refused (111 no Linux) | Nada escuta nessa porta, ou um firewall a rejeita | Verifique a porta; teste de outra rede |
CONNACK Not authorized / Bad user name or password | O WebSocket funciona, a autenticação MQTT falhou | Confira credenciais e ACLs |
Consulte os reason codes MQTT da última linha com o buscador de reason codes.
Como testar a conexão
Comece verificando o upgrade HTTP sem Python. Um endpoint WebSocket saudável responde 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/mqttUm 404 significa path errado, um 400 ou 426 geralmente indica que um header foi removido, e um erro de TLS significa que você está falando com a porta errada. Depois faça uma checagem de ponta a ponta: abra o cliente MQTT online, conecte via wss ao mesmo broker, assine testmqtt/py-ws/# e rode o ws_client.py. Se o navegador receber sua mensagem, Python, path e broker estão corretos. Em testar uma conexão MQTT via WebSocket há mais checagens, incluindo proxies e load balancers.
Assine no navegador e publique a partir do Python
Usando aiomqtt
Se o seu código é baseado em asyncio, o aiomqtt (que encapsula o paho) expõe as mesmas opções como argumentos do construtor:
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 sobre WebSockets em Python é um argumento no construtor mais a porta e o path certos. Use wss com verificação ativa, defina o path explicitamente para ninguém ter que adivinhar, configure proxies com proxy_set() ou mqtt_proxy e teste o upgrade com curl antes de culpar o seu código.
Perguntas frequentes
Qual porta e qual path o paho-mqtt usa para WebSockets?
O paho usa a porta que você passa em connect() e, por padrão, o path /mqtt. WebSockets sem criptografia costumam ficar na 8083 ou 80, os seguros na 8084 ou 443. Mude o path com client.ws_set_options(path=...) se o seu broker ou proxy usar outro.
O paho-mqtt respeita HTTPS_PROXY ou HTTP_PROXY?
Não. O paho só lê uma variável de ambiente chamada mqtt_proxy (com uma URL http:// ou socks://) e apenas se o PySocks estiver instalado. Fora isso, configure o proxy explicitamente com client.proxy_set(proxy_type=socks.HTTP, proxy_addr=..., proxy_port=...).
O que significa WebSocket handshake error, connection not upgraded?
O servidor respondeu à requisição HTTP de upgrade, mas não mudou para WebSocket. Normalmente o path está errado (uma página 404), você chegou a um servidor web ou proxy comum em vez do listener WebSocket do broker, ou um proxy removeu o header Upgrade.


