[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
# © 2025-2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
|
|
|
"""Mandataire inverse de développement devant Odoo, sans nginx.
|
|
|
|
|
|
|
|
|
|
Visé : Odoo 18. Une seule adresse pour le navigateur : les pages vont au
|
|
|
|
|
port web d'Odoo (8069), le bus — /websocket — à son port dédié (8072). Odoo,
|
|
|
|
|
lancé avec proxy_mode, lit les en-têtes X-Forwarded-* posés ici.
|
|
|
|
|
|
|
|
|
|
Le mandataire ne lit que la TÊTE de chaque requête : il choisit le port,
|
|
|
|
|
réécrit les en-têtes, puis relaie les octets tels quels dans les deux sens.
|
|
|
|
|
Le corps n'est ni lu en entier ni décompressé, une réponse gzip ou en
|
|
|
|
|
morceaux passe à l'identique, et une connexion montée en WebSocket reste
|
|
|
|
|
ouverte tant qu'un des deux bouts parle.
|
|
|
|
|
|
|
|
|
|
Chaque connexion porte UNE requête : l'amont reçoit « Connection: close »,
|
|
|
|
|
ferme après sa réponse, et le navigateur rouvre pour la suivante. Relayer
|
|
|
|
|
plusieurs requêtes sur une connexion demanderait de suivre la longueur de
|
|
|
|
|
chaque corps ; pour un outil de poste, une connexion de plus est moins
|
|
|
|
|
chère que cette comptabilité. En production, nginx (script/nginx/) garde
|
|
|
|
|
la main.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
import asyncio
|
[ADD] todo network: start the Odoo reverse proxy, local or network
Network gains a fourth entry that starts script/reverse_proxy/main.py
with the web and bus ports read from config.conf, falling back to the
older xmlrpc_port and longpolling_port names. It asks for this machine
only or the whole network, local by default, and warns when proxy_mode
is off or workers is 0: the first makes Odoo ignore X-Forwarded-*, the
second leaves no bus port, so /websocket fails behind the proxy too.
Checked: 48 tests pass (proxy, config reading, menu wiring, i18n); the
French screen renders with the real config.conf.
--- FR ---
[ADD] todo network : lancer le mandataire inverse Odoo, local ou réseau
Network gagne une quatrième entrée qui lance script/reverse_proxy/main.py
avec les ports web et bus lus dans config.conf, repli sur les anciens noms
xmlrpc_port et longpolling_port. Elle demande cette machine seule ou tout
le réseau, local par défaut, et prévient quand proxy_mode est éteint ou
workers vaut 0 : le premier fait ignorer X-Forwarded-* à Odoo, le second
ne laisse aucun port de bus, et /websocket échoue aussi derrière le proxy.
Vérifié : 48 tests passent (mandataire, lecture du config, câblage du
menu, i18n) ; l'écran français s'affiche avec le vrai config.conf.
Assisted-by: Claude Opus 5.5
2026-09-25 06:12:08 -04:00
|
|
|
import configparser
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
import ssl
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
import sys
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
import time
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
|
|
|
|
# Une tête au-delà est refusée (431) : aucun navigateur n'en envoie de si
|
|
|
|
|
# longue, et la lire en entier laisserait un client remplir la mémoire.
|
|
|
|
|
MAX_HEAD = 64 * 1024
|
|
|
|
|
|
|
|
|
|
DEFAULT_WEBSOCKET_PATHS = ("/websocket",)
|
|
|
|
|
|
|
|
|
|
# En-têtes propres à UN saut : jamais relayés tels quels. Upgrade et
|
|
|
|
|
# Connection sont reposés pour une montée en WebSocket.
|
|
|
|
|
HOP_BY_HOP = {
|
|
|
|
|
"connection",
|
|
|
|
|
"keep-alive",
|
|
|
|
|
"proxy-connection",
|
|
|
|
|
"proxy-authenticate",
|
|
|
|
|
"proxy-authorization",
|
|
|
|
|
"te",
|
|
|
|
|
"trailer",
|
|
|
|
|
"upgrade",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Posés par ce mandataire seul : la valeur d'un client est écartée, Odoo en
|
|
|
|
|
# proxy_mode prendrait sinon une adresse inventée pour celle du visiteur.
|
|
|
|
|
FORWARDED = {
|
|
|
|
|
"x-forwarded-for",
|
|
|
|
|
"x-forwarded-host",
|
|
|
|
|
"x-forwarded-proto",
|
|
|
|
|
"x-real-ip",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RELAY_CHUNK = 64 * 1024
|
|
|
|
|
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
# Délais de la mise en relation seulement, jamais du relais : une WebSocket
|
|
|
|
|
# ouverte reste muette des minutes entre deux notifications.
|
|
|
|
|
HEAD_TIMEOUT = 30.0
|
|
|
|
|
CONNECT_TIMEOUT = 10.0
|
|
|
|
|
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
class ProxyConfig:
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
"""Réglages du mandataire.
|
|
|
|
|
|
|
|
|
|
head_timeout borne l'attente de la tête d'une requête (408 au-delà),
|
|
|
|
|
connect_timeout la connexion à Odoo (504). trust_forwarded prolonge les
|
|
|
|
|
X-Forwarded-* reçus au lieu de les remplacer, pour un mandataire placé
|
|
|
|
|
derrière un autre. log reçoit une ligne par requête ; None le rend muet.
|
|
|
|
|
"""
|
|
|
|
|
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
odoo_host: str = "127.0.0.1"
|
|
|
|
|
web_port: int = 8069
|
|
|
|
|
websocket_port: int = 8072
|
|
|
|
|
websocket_paths: tuple = DEFAULT_WEBSOCKET_PATHS
|
|
|
|
|
forwarded_proto: str = "http"
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
head_timeout: float = HEAD_TIMEOUT
|
|
|
|
|
connect_timeout: float = CONNECT_TIMEOUT
|
|
|
|
|
trust_forwarded: bool = False
|
|
|
|
|
log: object = print
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
|
|
|
|
|
|
[ADD] todo network: start the Odoo reverse proxy, local or network
Network gains a fourth entry that starts script/reverse_proxy/main.py
with the web and bus ports read from config.conf, falling back to the
older xmlrpc_port and longpolling_port names. It asks for this machine
only or the whole network, local by default, and warns when proxy_mode
is off or workers is 0: the first makes Odoo ignore X-Forwarded-*, the
second leaves no bus port, so /websocket fails behind the proxy too.
Checked: 48 tests pass (proxy, config reading, menu wiring, i18n); the
French screen renders with the real config.conf.
--- FR ---
[ADD] todo network : lancer le mandataire inverse Odoo, local ou réseau
Network gagne une quatrième entrée qui lance script/reverse_proxy/main.py
avec les ports web et bus lus dans config.conf, repli sur les anciens noms
xmlrpc_port et longpolling_port. Elle demande cette machine seule ou tout
le réseau, local par défaut, et prévient quand proxy_mode est éteint ou
workers vaut 0 : le premier fait ignorer X-Forwarded-* à Odoo, le second
ne laisse aucun port de bus, et /websocket échoue aussi derrière le proxy.
Vérifié : 48 tests passent (mandataire, lecture du config, câblage du
menu, i18n) ; l'écran français s'affiche avec le vrai config.conf.
Assisted-by: Claude Opus 5.5
2026-09-25 06:12:08 -04:00
|
|
|
def read_odoo_config(path):
|
|
|
|
|
"""Les ports et réglages d'Odoo utiles au mandataire.
|
|
|
|
|
|
|
|
|
|
Lit la section [options] d'un config.conf. Les anciens noms, xmlrpc_port
|
|
|
|
|
et longpolling_port, servent de repli ; une option absente
|
|
|
|
|
ou illisible — un fichier absent compris — garde le défaut d'Odoo.
|
|
|
|
|
|
|
|
|
|
:return: {"web_port", "websocket_port", "proxy_mode", "workers"}
|
|
|
|
|
"""
|
|
|
|
|
cfg = configparser.ConfigParser(interpolation=None)
|
|
|
|
|
cfg.read(path)
|
|
|
|
|
|
|
|
|
|
def entier(noms, defaut):
|
|
|
|
|
for nom in noms:
|
|
|
|
|
try:
|
|
|
|
|
return cfg.getint("options", nom)
|
|
|
|
|
except (configparser.Error, ValueError):
|
|
|
|
|
continue
|
|
|
|
|
return defaut
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
proxy_mode = cfg.getboolean("options", "proxy_mode")
|
|
|
|
|
except (configparser.Error, ValueError):
|
|
|
|
|
proxy_mode = False
|
|
|
|
|
return {
|
|
|
|
|
"web_port": entier(("http_port", "xmlrpc_port"), 8069),
|
|
|
|
|
"websocket_port": entier(("gevent_port", "longpolling_port"), 8072),
|
|
|
|
|
"proxy_mode": proxy_mode,
|
|
|
|
|
"workers": entier(("workers",), 0),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
def parse_head(head):
|
|
|
|
|
"""Découpe une tête de requête brute.
|
|
|
|
|
|
|
|
|
|
:param head: octets jusqu'à la ligne vide « \\r\\n\\r\\n » comprise
|
|
|
|
|
:return: (méthode, cible, version, [(nom, valeur), …]) dans l'ordre reçu
|
|
|
|
|
:raises ValueError: ligne de requête ou en-tête illisible
|
|
|
|
|
"""
|
|
|
|
|
lines = head.decode("latin-1").split("\r\n")
|
|
|
|
|
parts = lines[0].split(" ")
|
|
|
|
|
if len(parts) != 3 or not parts[2].startswith("HTTP/"):
|
|
|
|
|
raise ValueError("ligne de requête illisible")
|
|
|
|
|
method, target, version = parts
|
|
|
|
|
headers = []
|
|
|
|
|
for line in lines[1:]:
|
|
|
|
|
if not line:
|
|
|
|
|
continue
|
|
|
|
|
name, sep, value = line.partition(":")
|
|
|
|
|
if not sep or not name or name != name.strip():
|
|
|
|
|
raise ValueError("en-tête illisible")
|
|
|
|
|
headers.append((name, value.strip()))
|
|
|
|
|
return method, target, version, headers
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def is_websocket_path(target, config):
|
|
|
|
|
"""Vrai quand la cible vise le bus, comparée par segment entier.
|
|
|
|
|
|
|
|
|
|
« /websocket » et « /websocket/… » vont au bus, « /websocketX » non.
|
|
|
|
|
"""
|
|
|
|
|
path = target.split("?", 1)[0]
|
|
|
|
|
return any(
|
|
|
|
|
path == p or path.startswith(p.rstrip("/") + "/")
|
|
|
|
|
for p in config.websocket_paths
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def is_upgrade(headers):
|
|
|
|
|
"""Vrai quand la requête demande une montée de protocole (WebSocket)."""
|
|
|
|
|
connection = ",".join(
|
|
|
|
|
v for n, v in headers if n.lower() == "connection"
|
|
|
|
|
).lower()
|
|
|
|
|
has_upgrade = any(n.lower() == "upgrade" for n, _ in headers)
|
|
|
|
|
return has_upgrade and "upgrade" in connection
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def rewrite_head(method, target, version, headers, client_ip, config):
|
|
|
|
|
"""La tête envoyée à Odoo.
|
|
|
|
|
|
|
|
|
|
Retire les en-têtes d'un saut et ceux du mandataire venus du client, pose
|
|
|
|
|
X-Forwarded-For/-Host/-Proto et X-Real-IP, puis la conduite de connexion :
|
|
|
|
|
« Upgrade » et « Connection: Upgrade » pour une montée en WebSocket,
|
|
|
|
|
« Connection: close » sinon.
|
|
|
|
|
|
|
|
|
|
:return: la tête en octets, ligne vide finale comprise
|
|
|
|
|
"""
|
|
|
|
|
upgrade = is_upgrade(headers)
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
|
|
|
|
|
def first(name):
|
|
|
|
|
return next((v for n, v in headers if n.lower() == name), "")
|
|
|
|
|
|
|
|
|
|
upgrade_value = first("upgrade")
|
|
|
|
|
host = first("host")
|
|
|
|
|
forwarded_for = client_ip
|
|
|
|
|
real_ip = client_ip
|
|
|
|
|
forwarded_host = host
|
|
|
|
|
forwarded_proto = config.forwarded_proto
|
|
|
|
|
if config.trust_forwarded:
|
|
|
|
|
# Derrière un autre mandataire : sa chaîne est prolongée, et ce qu'il
|
|
|
|
|
# dit de l'hôte et du protocole du visiteur l'emporte.
|
|
|
|
|
chain = ", ".join(
|
|
|
|
|
v for n, v in headers if n.lower() == "x-forwarded-for"
|
|
|
|
|
)
|
|
|
|
|
if chain:
|
|
|
|
|
forwarded_for = f"{chain}, {client_ip}"
|
|
|
|
|
real_ip = chain.split(",")[0].strip()
|
|
|
|
|
real_ip = first("x-real-ip") or real_ip
|
|
|
|
|
forwarded_host = first("x-forwarded-host") or host
|
|
|
|
|
forwarded_proto = first("x-forwarded-proto") or forwarded_proto
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
kept = [
|
|
|
|
|
(n, v)
|
|
|
|
|
for n, v in headers
|
|
|
|
|
if n.lower() not in HOP_BY_HOP and n.lower() not in FORWARDED
|
|
|
|
|
]
|
|
|
|
|
kept += [
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
("X-Forwarded-For", forwarded_for),
|
|
|
|
|
("X-Real-IP", real_ip),
|
|
|
|
|
("X-Forwarded-Host", forwarded_host),
|
|
|
|
|
("X-Forwarded-Proto", forwarded_proto),
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
]
|
|
|
|
|
if upgrade:
|
|
|
|
|
kept += [("Upgrade", upgrade_value), ("Connection", "Upgrade")]
|
|
|
|
|
else:
|
|
|
|
|
kept.append(("Connection", "close"))
|
|
|
|
|
lines = [f"{method} {target} {version}"]
|
|
|
|
|
lines += [f"{n}: {v}" for n, v in kept]
|
|
|
|
|
return ("\r\n".join(lines) + "\r\n\r\n").encode("latin-1")
|
|
|
|
|
|
|
|
|
|
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
async def _reply_error(writer, status, reason, detail=""):
|
|
|
|
|
body = f"{status} {reason}\n{detail}".encode()
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
writer.write(
|
|
|
|
|
f"HTTP/1.1 {status} {reason}\r\nContent-Type: text/plain\r\n"
|
|
|
|
|
f"Content-Length: {len(body)}\r\nConnection: close\r\n\r\n".encode()
|
|
|
|
|
+ body
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
await writer.drain()
|
|
|
|
|
except ConnectionError:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
async def _pipe(reader, writer, half_close, on_first=None):
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
"""Copie reader vers writer jusqu'à la fin du flux.
|
|
|
|
|
|
|
|
|
|
half_close : à la fin, fermer seulement l'écriture (write_eof) plutôt que
|
|
|
|
|
rien — le client qui a fini d'envoyer attend encore la réponse.
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
on_first reçoit le premier bloc lu, sans le retenir : le journal y lit
|
|
|
|
|
le statut de la réponse.
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
while data := await reader.read(RELAY_CHUNK):
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
if on_first is not None:
|
|
|
|
|
on_first(data)
|
|
|
|
|
on_first = None
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
writer.write(data)
|
|
|
|
|
await writer.drain()
|
|
|
|
|
if half_close and writer.can_write_eof():
|
|
|
|
|
writer.write_eof()
|
|
|
|
|
except (ConnectionError, OSError):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
def _status_of(chunk):
|
|
|
|
|
"""Le statut d'une réponse d'après son premier bloc, « ? » sinon."""
|
|
|
|
|
parts = chunk.split(b" ", 2)
|
|
|
|
|
if len(parts) >= 2 and parts[0].startswith(b"HTTP/"):
|
|
|
|
|
return parts[1].decode("latin-1")
|
|
|
|
|
return "?"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def unreachable_detail(config, port):
|
|
|
|
|
"""Pourquoi Odoo ne répond pas sur ce port, en une ligne."""
|
|
|
|
|
where = f"{config.odoo_host}:{port}"
|
|
|
|
|
if port == config.websocket_port:
|
|
|
|
|
return (
|
|
|
|
|
f"Odoo ne répond pas sur {where} (bus). Le bus n'écoute que si"
|
|
|
|
|
" Odoo tourne avec workers >= 1.\n"
|
|
|
|
|
)
|
|
|
|
|
return f"Odoo ne répond pas sur {where} (web). Odoo est-il démarré ?\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _open_upstream(host, port):
|
|
|
|
|
return await asyncio.open_connection(host, port)
|
|
|
|
|
|
|
|
|
|
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
async def handle(reader, writer, config):
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
"""Sert une connexion cliente : une requête, relayée puis fermée.
|
|
|
|
|
|
|
|
|
|
Une ligne de journal par requête : client, méthode, cible, route (web
|
|
|
|
|
ou bus), statut et durée — celle d'une WebSocket va jusqu'à sa fermeture.
|
|
|
|
|
"""
|
|
|
|
|
started = time.monotonic()
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
peer = writer.get_extra_info("peername")
|
|
|
|
|
client_ip = peer[0] if peer else ""
|
|
|
|
|
upstream_writer = None
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
request = "-"
|
|
|
|
|
route = "-"
|
|
|
|
|
status = {"code": "?"}
|
|
|
|
|
|
|
|
|
|
def journal():
|
|
|
|
|
if config.log is not None:
|
|
|
|
|
ms = int((time.monotonic() - started) * 1000)
|
|
|
|
|
config.log(
|
|
|
|
|
f"{client_ip} {request} → {route} {status['code']} {ms} ms"
|
|
|
|
|
)
|
|
|
|
|
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
try:
|
|
|
|
|
try:
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
head = await asyncio.wait_for(
|
|
|
|
|
reader.readuntil(b"\r\n\r\n"), config.head_timeout
|
|
|
|
|
)
|
|
|
|
|
except asyncio.TimeoutError:
|
|
|
|
|
await _reply_error(writer, 408, "Request Timeout")
|
|
|
|
|
return
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
except asyncio.LimitOverrunError:
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
status["code"] = "431"
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
await _reply_error(writer, 431, "Request Header Fields Too Large")
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
journal()
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
return
|
|
|
|
|
except asyncio.IncompleteReadError:
|
|
|
|
|
return
|
|
|
|
|
try:
|
|
|
|
|
method, target, version, headers = parse_head(head)
|
|
|
|
|
except ValueError:
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
status["code"] = "400"
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
await _reply_error(writer, 400, "Bad Request")
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
journal()
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
return
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
request = f"{method} {target}"
|
|
|
|
|
bus = is_websocket_path(target, config)
|
|
|
|
|
route = "bus" if bus else "web"
|
|
|
|
|
port = config.websocket_port if bus else config.web_port
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
try:
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
upstream_reader, upstream_writer = await asyncio.wait_for(
|
|
|
|
|
_open_upstream(config.odoo_host, port), config.connect_timeout
|
|
|
|
|
)
|
|
|
|
|
except asyncio.TimeoutError:
|
|
|
|
|
status["code"] = "504"
|
|
|
|
|
await _reply_error(
|
|
|
|
|
writer,
|
|
|
|
|
504,
|
|
|
|
|
"Gateway Timeout",
|
|
|
|
|
unreachable_detail(config, port),
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
)
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
journal()
|
|
|
|
|
return
|
|
|
|
|
except OSError:
|
|
|
|
|
status["code"] = "502"
|
|
|
|
|
await _reply_error(
|
|
|
|
|
writer, 502, "Bad Gateway", unreachable_detail(config, port)
|
|
|
|
|
)
|
|
|
|
|
journal()
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
return
|
|
|
|
|
upstream_writer.write(
|
|
|
|
|
rewrite_head(method, target, version, headers, client_ip, config)
|
|
|
|
|
)
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
|
|
|
|
|
def note_status(chunk):
|
|
|
|
|
status["code"] = _status_of(chunk)
|
|
|
|
|
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
# Ce que le client a déjà envoyé après la tête — un début de corps —
|
|
|
|
|
# est dans le tampon du lecteur : _pipe le relaie en premier.
|
|
|
|
|
to_client = asyncio.create_task(
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
_pipe(
|
|
|
|
|
upstream_reader, writer, half_close=False, on_first=note_status
|
|
|
|
|
)
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
)
|
|
|
|
|
to_odoo = asyncio.create_task(
|
|
|
|
|
_pipe(reader, upstream_writer, half_close=True)
|
|
|
|
|
)
|
|
|
|
|
# La fin de la réponse clôt l'échange ; un client qui ferme le premier
|
|
|
|
|
# (onglet fermé, WebSocket quittée) le clôt aussi.
|
|
|
|
|
done, _ = await asyncio.wait(
|
|
|
|
|
{to_client, to_odoo}, return_when=asyncio.FIRST_COMPLETED
|
|
|
|
|
)
|
|
|
|
|
if to_odoo in done and not to_client.done():
|
|
|
|
|
# Le client a fini d'envoyer : la réponse peut encore venir.
|
|
|
|
|
await to_client
|
|
|
|
|
for task in (to_client, to_odoo):
|
|
|
|
|
task.cancel()
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
journal()
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
finally:
|
|
|
|
|
for w in (upstream_writer, writer):
|
|
|
|
|
if w is not None:
|
|
|
|
|
w.close()
|
|
|
|
|
|
|
|
|
|
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
async def serve(config, listen, port, ssl_context=None):
|
|
|
|
|
"""Démarre l'écoute et rend le serveur asyncio, déjà à l'écoute.
|
|
|
|
|
|
|
|
|
|
ssl_context : écoute en HTTPS ; Odoo, derrière, reste en HTTP clair.
|
|
|
|
|
"""
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
return await asyncio.start_server(
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
lambda r, w: handle(r, w, config),
|
|
|
|
|
listen,
|
|
|
|
|
port,
|
|
|
|
|
limit=MAX_HEAD,
|
|
|
|
|
ssl=ssl_context,
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
def make_ssl_context(cert, key):
|
|
|
|
|
"""Contexte serveur TLS depuis un certificat et sa clé (PEM)."""
|
|
|
|
|
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
|
|
|
|
context.load_cert_chain(cert, key)
|
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def probe(config):
|
|
|
|
|
"""Tente une connexion aux deux ports d'Odoo.
|
|
|
|
|
|
|
|
|
|
:return: {"web": bool, "bus": bool}, vrai quand le port accepte
|
|
|
|
|
"""
|
|
|
|
|
result = {}
|
|
|
|
|
for role, port in (
|
|
|
|
|
("web", config.web_port),
|
|
|
|
|
("bus", config.websocket_port),
|
|
|
|
|
):
|
|
|
|
|
try:
|
|
|
|
|
_, w = await asyncio.wait_for(
|
|
|
|
|
_open_upstream(config.odoo_host, port), config.connect_timeout
|
|
|
|
|
)
|
|
|
|
|
w.close()
|
|
|
|
|
result[role] = True
|
|
|
|
|
except (OSError, asyncio.TimeoutError):
|
|
|
|
|
result[role] = False
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def startup_warnings(state, config):
|
|
|
|
|
"""Les avertissements à dire au démarrage d'après probe(), [] sinon."""
|
|
|
|
|
warnings = []
|
|
|
|
|
if not state.get("web"):
|
|
|
|
|
warnings.append(unreachable_detail(config, config.web_port).strip())
|
|
|
|
|
if not state.get("bus"):
|
|
|
|
|
warnings.append(
|
|
|
|
|
unreachable_detail(config, config.websocket_port).strip()
|
|
|
|
|
)
|
|
|
|
|
return warnings
|
|
|
|
|
|
|
|
|
|
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
def get_config(argv=None):
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
|
description=(
|
|
|
|
|
"Mandataire inverse de développement devant Odoo : pages vers le"
|
|
|
|
|
" port web, bus vers le port websocket. En production, préférer"
|
|
|
|
|
" nginx (script/nginx/)."
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--listen",
|
|
|
|
|
default="127.0.0.1",
|
|
|
|
|
help="Adresse d'écoute ; 0.0.0.0 pour l'exposer au réseau.",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument("--port", type=int, default=8080)
|
|
|
|
|
parser.add_argument("--odoo-host", default="127.0.0.1")
|
|
|
|
|
parser.add_argument("--web-port", type=int, default=8069)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--websocket-port",
|
|
|
|
|
type=int,
|
|
|
|
|
default=8072,
|
|
|
|
|
help="Port du bus d'Odoo (gevent).",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--websocket-path",
|
|
|
|
|
action="append",
|
|
|
|
|
help=(
|
|
|
|
|
"Chemin routé vers le port du bus ; répétable. Défaut :"
|
|
|
|
|
f" {', '.join(DEFAULT_WEBSOCKET_PATHS)}."
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--forwarded-proto",
|
|
|
|
|
choices=("http", "https"),
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
help=(
|
|
|
|
|
"Valeur de X-Forwarded-Proto. Défaut : https avec --tls-cert,"
|
|
|
|
|
" http sinon."
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--tls-cert",
|
|
|
|
|
help="Certificat PEM : le mandataire écoute alors en HTTPS.",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument("--tls-key", help="Clé PEM du certificat.")
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--trust-forwarded",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help=(
|
|
|
|
|
"Prolonger les X-Forwarded-* reçus au lieu de les remplacer, pour"
|
|
|
|
|
" un mandataire placé derrière un autre. À éviter face à des"
|
|
|
|
|
" navigateurs : ils pourraient se faire passer pour une autre"
|
|
|
|
|
" adresse."
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--quiet",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="Ne pas écrire une ligne par requête.",
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
)
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
args = parser.parse_args(argv)
|
|
|
|
|
if bool(args.tls_cert) != bool(args.tls_key):
|
|
|
|
|
parser.error("--tls-cert et --tls-key vont ensemble")
|
|
|
|
|
return args
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def config_from_args(args):
|
|
|
|
|
return ProxyConfig(
|
|
|
|
|
odoo_host=args.odoo_host,
|
|
|
|
|
web_port=args.web_port,
|
|
|
|
|
websocket_port=args.websocket_port,
|
|
|
|
|
websocket_paths=tuple(args.websocket_path or DEFAULT_WEBSOCKET_PATHS),
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
forwarded_proto=args.forwarded_proto
|
|
|
|
|
or ("https" if args.tls_cert else "http"),
|
|
|
|
|
trust_forwarded=args.trust_forwarded,
|
|
|
|
|
log=None if args.quiet else print,
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _run(args):
|
|
|
|
|
config = config_from_args(args)
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
context = None
|
|
|
|
|
if args.tls_cert:
|
|
|
|
|
context = make_ssl_context(args.tls_cert, args.tls_key)
|
|
|
|
|
server = await serve(config, args.listen, args.port, ssl_context=context)
|
|
|
|
|
scheme = "https" if context else "http"
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
print(
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
f"Mandataire sur {scheme}://{args.listen}:{args.port} → pages"
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
f" {config.odoo_host}:{config.web_port}, bus"
|
|
|
|
|
f" {config.odoo_host}:{config.websocket_port}"
|
|
|
|
|
f" ({', '.join(config.websocket_paths)})."
|
|
|
|
|
" Odoo doit tourner avec proxy_mode = True."
|
|
|
|
|
)
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
for warning in startup_warnings(await probe(config), config):
|
|
|
|
|
print(f"⚠️ {warning}")
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
async with server:
|
|
|
|
|
await server.serve_forever()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main(argv=None):
|
[IMP] reverse proxy: timeouts, access log, startup probe, TLS, chain
A client that opened a connection and sent nothing held its task forever,
and the proxy stayed silent until a 502 that named no port. The request
head now waits 30 s (408) and the connection to Odoo 10 s (504), never
the relay, so a WebSocket stays open; a line per request gives route,
status and duration; the start probes both ports and a 502 names the one
missing, with workers for the bus. --tls-cert/--tls-key serve HTTPS and
default X-Forwarded-Proto to https; --trust-forwarded extends a chain.
Checked: 32 tests; live Odoo 18 over HTTPS, wss ping returns its pong.
--- FR ---
[IMP] reverse proxy : délais, journal, sonde au démarrage, TLS, chaîne
Un client qui ouvrait une connexion sans rien envoyer gardait sa tâche
pour toujours, et le mandataire restait muet jusqu'à un 502 sans port. La
tête attend désormais 30 s (408) et la connexion à Odoo 10 s (504), jamais
le relais : une WebSocket reste ouverte ; une ligne par requête donne
route, statut et durée ; le démarrage sonde les deux ports et un 502 nomme
celui qui manque, workers pour le bus. --tls-cert/--tls-key servent en
HTTPS, X-Forwarded-Proto à https ; --trust-forwarded prolonge une chaîne.
Vérifié : 32 tests ; Odoo 18 réel en HTTPS, un ping wss rend son pong.
Assisted-by: Claude Opus 5.5
2026-09-25 06:35:29 -04:00
|
|
|
# Une ligne de journal doit paraître à sa requête, y compris quand la
|
|
|
|
|
# sortie est un tube ou un fichier, que Python tamponne sinon par blocs.
|
|
|
|
|
sys.stdout.reconfigure(line_buffering=True)
|
[ADD] reverse proxy: dev front for Odoo 18, websocket relayed both ways
A single local address routes pages to the Odoo web port and /websocket
to the bus port, with X-Forwarded-* set for proxy_mode; a client value
for these headers is replaced. Only the request head is read: bytes then
pass through unchanged in both directions, so an upgraded WebSocket stays
open and a gzip or chunked response reaches the browser intact. Each
connection carries one request; production keeps nginx (script/nginx).
Checked: 15 tests on real sockets (routing, headers, 5 MB body, gzip,
WebSocket echo, 502, 400, 431); byte-identical to direct against Odoo 18.
--- FR ---
[ADD] reverse proxy : façade de dev pour Odoo 18, websocket dans 2 sens
Une seule adresse locale envoie les pages au port web d'Odoo et
/websocket au port du bus, avec les X-Forwarded-* de proxy_mode ; la
valeur qu'un client donne à ces en-têtes est remplacée. Seule la tête est
lue : les octets passent ensuite tels quels dans les deux sens, une
WebSocket montée reste ouverte et une réponse gzip ou en morceaux arrive
intacte. Une requête par connexion ; la production garde nginx.
Vérifié : 15 tests sur vrais sockets (routage, en-têtes, corps de 5 Mo,
gzip, écho WebSocket, 502, 400, 431) ; identique octet pour octet au
direct face à Odoo 18.
Assisted-by: Claude Opus 5.5
2026-09-25 05:25:16 -04:00
|
|
|
try:
|
|
|
|
|
asyncio.run(_run(get_config(argv)))
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
pass
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
sys.exit(main())
|