nginx + fastCGI + Django - obtention d'une corruption de données dans les réponses envoyées au client

10

J'exécute Django derrière nginx en utilisant FastCGI. J'ai découvert que dans certaines des réponses envoyées au client, une corruption de données aléatoire se produit au milieu des réponses (peut-être quelques centaines d'octets au milieu).

À ce stade, je l'ai réduit à un bug dans le gestionnaire FastCGI de nginx ou dans le gestionnaire FastCGI de Django (c'est-à-dire probablement un bogue en flup), car ce problème ne se produit jamais lorsque j'exécute le serveur Django en mode autonome (c'est-à-dire runserver). Cela ne se produit qu'en mode FastCGI.

Autres tendances intéressantes:

  • Cela a tendance à se produire sur des réponses plus importantes. Lorsqu'un client se connecte pour la première fois, il reçoit un groupe de blocs de 1 Mo pour les synchroniser avec la base de données du serveur. Après cette première synchronisation, les réponses sont beaucoup plus petites (généralement quelques Ko à la fois). La corruption semble toujours se produire sur ces morceaux de 1 Mo envoyés au début.

  • Cela se produit plus souvent lorsque le client est connecté au serveur via LAN (c'est-à-dire une connexion à faible latence et à large bande passante). Cela me fait penser qu'il existe une sorte de condition de concurrence dans nginx ou flup qui est exacerbée par un débit de données accru.

En ce moment, j'ai dû contourner cela en mettant un résumé SHA1 supplémentaire dans l'en-tête de réponse et en demandant au client de rejeter les réponses lorsque l'en-tête ne correspond pas à la somme de contrôle du corps, mais c'est une sorte de solution horrible.

Quelqu'un d'autre a-t-il vécu quelque chose comme ça, ou a-t-il des conseils sur la façon d'identifier si c'est flup ou nginx qui est en cause ici afin que je puisse déposer un bogue avec l'équipe appropriée?

Merci d'avance pour votre aide.

Remarque: j'ai également publié un bug similaire dans lighttpd + FastCGI + Django il y a quelque temps ici: /programming/3714489/lighttpd-fastcgi-django-truncated-response-sent-to-client-due-to -inattendu ... même si ce n'est pas la même chose (troncature vs corruption), ça commence à ressembler au coupable commun est flup / Django plutôt que le serveur web ..

Edit: je devrais également noter quel est mon environnement:

  • OSX 10.6.6 sur un Mac Mini

  • Python 2.6.1 (système)

  • Django 1.3 (du tarball officiel)

  • flup 1.0.2 (de l'oeuf Python sur le site de flup)

  • nginx + ssl 1.0.0 (de Macports)

EDIT: En réponse au commentaire de Jerzyk, le chemin de code qui assemble la réponse ressemble (édité pour être succinct):

# This returns an objc NSData object, which is an array.array 
# when pushed through the PyObjC bridge
ret = handler( request ) 

response = HttpResponse( ret )
response[ "Content-Length" ] = len( ret )
return response

Je ne pense pas qu'il soit possible que la longueur du contenu soit erronée sur cette base, et AFAIK il n'y a aucun moyen de marquer un objet Django HttpResponse comme explicitement binaire par opposition au texte. De plus, comme le problème ne se produit que de manière intermittente, je ne pense pas que cela l'explique autrement, vous le verriez probablement à chaque demande.

EDIT @ionelmc: Vous devez définir la longueur du contenu dans Django - nginx ne le définit pas pour vous, comme dans l'exemple ci-dessous une fois que j'ai désactivé la définition de la longueur du contenu explicitement:

$ curl -i http://localhost/io/ping
HTTP/1.1 200 OK
Server: nginx/1.0.0
Date: Thu, 23 Jun 2011 13:37:14 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive

AKSJDHAKLSJDHKLJAHSD
glenc
la source
Si les morceaux initiaux ne changent pas souvent ou ne sont pas spécifiques à l'utilisateur, il est peut-être préférable d'écrire sur le disque et de servir directement via nginx?
sunn0
Malheureusement, les morceaux sont à la fois spécifiques à l'utilisateur et changent fréquemment, donc aucune mise en cache de ce type ne serait appropriée pour cette application. Je suis également désireux de découvrir ce qui cause réellement cette corruption de données plutôt que de simplement le contourner (ce que je fais déjà avec le résumé SHA1 supplémentaire dans l'en-tête).
glenc
Je peux penser à deux raisons possibles: encodage incorrect - HttpRespose comme texte vs en-têtes binaires ou incorrects (en particulier la longueur du contenu)
Jerzyk
1
@glenc quel est un type de contenu pour cette réponse? si c'est binaire - pouvez-vous essayer de le définir? (par exemple mimetype = 'application / x-ms-excel' ou autre)
Jerzyk
2
Vous n'avez pas besoin de définir la longueur du contenu si votre Transfer-Encoding est fragmenté. rfc 2616 l'interdit explicitement: "Le champ d'en-tête Content-Length NE DOIT PAS être envoyé si ces deux longueurs sont différentes (c'est-à-dire si un champ d'en-tête Transfer-Encoding est présent)."
ionelmc

Réponses:

1

Avez-vous une sorte de directive de mise en cache nginx (bypass / no_cache) active pour les réponses fastcgi?

Dans nginx '1.0.3 Changenotes, ils ont corrigé une corruption de réponse:

Correctif: une réponse mise en cache peut être rompue si les valeurs des directives "proxy / fastcgi / scgi / uwsgi_cache_bypass" et "proxy / fastcgi / scgi / uwsgi_no_cache" étaient différentes; le bogue était apparu en 0.8.46.

Source: http://nginx.org/en/CHANGES (section 1.0.3.)

Michel Feldheim
la source
0

Peut-être que la corruption occasionnelle ne se produit que si la sortie contient au moins un caractère UTF-8.

Content-length et string length ne sont pas la même chose, car un caractère UTF-8 peut contenir de 2 à 5 octets.

Andy Lee Robinson
la source
Hmmmm .. bien que cela soit vrai, cela ne semble pas être la cause car la corruption se produisait au milieu des blocs de données et n'était pas simplement un cas de données manquantes à la fin.
glenc
0

Une façon de résoudre ce cas un peu plus serait de:

  • avoir nginx et django en cours d'exécution sur un matériel différent (afin que vous puissiez facilement capturer le trafic)
  • capturer le trafic du client vers - / -> nginx et nginx - / -> django (c'est-à-dire utiliser Wirehark)

Une fois que vous avez détecté une erreur côté client (basée sur sha1), accédez à la capture réseau, examinez le flux enregistré (TCP) et essayez de savoir si le problème est généré par nginx ou provient-il (directement) de django .

cipy
la source
0

J'ai eu un problème très similaire qui me tourmentait depuis aussi longtemps que j'avais cette configuration. Comme vous, j'utilise FastCGI, Nginx et macOS, et j'ai trouvé une corruption aléatoire au milieu de demandes importantes (c'était environ 2% des demandes d'un document de 1,5 Mo).

J'ai pu résoudre mon problème en passant aux sockets Unix via TCP pour la connexion FastCGI entre PHP-FPM (dans mon cas) et Nginx. Je ne sais pas quelle pièce du puzzle est responsable de la corruption, mais éviter la connexion TCP interne l'a corrigé.

Robert
la source