nginx + fastCGI + Django - uzyskiwanie uszkodzenia danych w odpowiedziach wysyłanych do klienta

10

Używam Django za nginx za pomocą FastCGI. Odkryłem, że w niektórych odpowiedziach wysyłanych do klienta przypadkowe uszkodzenie danych występuje w środku odpowiedzi (może to być kilkaset bajtów w środku).

W tym momencie zawęziłem to do bycia błędem w module obsługi FastCGI nginx lub w module obsługi FastCGI Django (tj. Prawdopodobnie błąd we flupie), ponieważ problem ten nigdy nie występuje, gdy uruchamiam serwer Django w trybie autonomicznym (tj. runserver). Zdarza się to tylko w trybie FastCGI.

Inne ciekawe trendy:

  • Zdarza się to przy większych odpowiedziach. Gdy klient loguje się po raz pierwszy, wysyłany jest pakiet 1 MB fragmentów w celu zsynchronizowania ich z bazą danych serwera. Po tej pierwszej synchronizacji odpowiedzi są znacznie mniejsze (zwykle kilka KB na raz). Korupcja zawsze zdarza się w przypadku tych 1 MB fragmentów wysłanych na początku.

  • Zdarza się to częściej, gdy klient jest podłączony do serwera za pośrednictwem sieci LAN (tj. Połączenie o niskim opóźnieniu i dużej przepustowości). To sprawia, że ​​myślę, że w nginx lub flup występuje jakiś warunek wyścigowy, który jest pogarszany przez zwiększoną szybkość transmisji danych.

W tej chwili musiałem obejść ten problem, umieszczając dodatkowy skrót SHA1 w nagłówku odpowiedzi i każąc klientowi odrzucić odpowiedzi, w których nagłówek nie zgadza się z sumą kontrolną treści, ale jest to trochę okropne rozwiązanie.

Czy ktoś jeszcze doświadczył czegoś takiego lub ma jakieś wskazówki, jak ustalić, czy to wina Flupa, czy Nginxa, więc mogę zgłosić błąd do odpowiedniego zespołu?

Z góry dziękuję za wszelką pomoc.

Uwaga: Podobny błąd opublikowałem także w lighttpd + FastCGI + Django jakiś czas tutaj: /programming/3714489/lighttpd-fastcgi-django-truncated-response-sent-to-client-due-to - nieoczekiwany ... mimo że nie jest to to samo (obcinanie a uszkodzenie), zaczyna wyglądać, jakby zwykłym winowajcą był flup / Django, a nie serwer WWW.

Edycja: Powinienem też zauważyć, jakie jest moje środowisko:

  • OSX 10.6.6 na komputerze Mac Mini

  • Python 2.6.1 (system)

  • Django 1.3 (z oficjalnego tarball)

  • flup 1.0.2 (z jaja Python na stronie flup)

  • nginx + ssl 1.0.0 (z Macports)

EDYCJA: W odpowiedzi na komentarz Jerzyka wygląda ścieżka kodu, która składa odpowiedź (edytowana dla zwięzłości):

# 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

Nie sądzę, że jest możliwe, aby na tej podstawie długość treści była błędna, a AFAIK nie ma sposobu, aby oznaczyć obiekt Django HttpResponse jako jawnie binarny w przeciwieństwie do tekstu. Ponadto, ponieważ problem występuje tylko sporadycznie, nie sądzę, że wyjaśnia to inaczej, prawdopodobnie można go zobaczyć przy każdym żądaniu.

EDYCJA @ionelmc: Musisz ustawić Content-Length w Django - nginx nie ustawia tego dla ciebie, jak w poniższym przykładzie, kiedy wyraźnie wyłączyłem ustawienie Content-Length:

$ 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
źródło
Jeśli początkowe porcje nie zmieniają się często lub nie są specyficzne dla użytkownika, może pisanie na dysk i podawanie bezpośrednio przez nginx jest lepszym sposobem?
sunn0
Niestety, porcje są specyficzne dla użytkownika i często się zmieniają, więc żadne buforowanie tego rodzaju nie byłoby odpowiednie dla tej aplikacji. Zależy mi również na tym, aby dowiedzieć się, co tak naprawdę powoduje uszkodzenie danych, zamiast po prostu obejść je (co już robię z dodatkowym skrótem SHA1 w nagłówku).
glenc
Mogę myśleć o dwóch możliwych przyczyn: złego kodowania - HttpRespose jako binarne tekst vs. lub błędnych nagłówków (zwłaszcza Content-Length)
Jerzyk
1
@glenc jaki jest typ zawartości dla tej odpowiedzi? jeśli jest to plik binarny - czy możesz spróbować go ustawić? (np. mimetype = 'application / x-ms-excel' lub inaczej)
Jerzyk
2
Nie musisz ustawiać długości treści, jeśli kodowanie przesyłania jest podzielone na części. rfc 2616 wyraźnie zabrania tego: „Pole nagłówka Content-Length NIE MOŻE być wysyłane, jeśli te dwie długości są różne (tj. jeśli występuje pole nagłówka Kodowanie przesyłania)”.
ionelmc

Odpowiedzi:

1

Czy masz jakąś dyrektywę buforowania nginx (bypass / no_cache) aktywną dla odpowiedzi fastcgi?

W Changenotes 1.0.3 nginx naprawili uszkodzenie odpowiedzi:

Poprawka: buforowana odpowiedź może zostać uszkodzona, jeśli wartości dyrektywy „proxy / fastcgi / scgi / uwsgi_cache_bypass” i „proxy / fastcgi / scgi / uwsgi_no_cache” były różne; błąd pojawił się w wersji 0.8.46.

Źródło: http://nginx.org/en/CHANGES (sekcja 1.0.3.)

Michel Feldheim
źródło
0

Być może sporadyczne uszkodzenie występuje tylko wtedy, gdy wyjście zawiera co najmniej jeden znak UTF-8.

Długość treści i długość łańcucha nie są tym samym, ponieważ jeden znak UTF-8 może zawierać od 2 do 5 bajtów.

Andy Lee Robinson
źródło
Hmmmm .. chociaż jest to prawda, nie wydaje się prawdopodobne, aby była przyczyną, ponieważ uszkodzenie miało miejsce w środku fragmentów danych i nie było po prostu przypadkiem braku danych na końcu.
glenc
0

Jednym ze sposobów rozwiązania tego problemu jest:

  • Nginx i Django działają na innym sprzęcie (abyś mógł łatwo przechwytywać ruch)
  • przechwyć ruch z klienta do - / -> nginx i nginx - / -> django (tj. użyj wireshark)

Po wykryciu błędu po stronie klienta (na podstawie sha1), przejdź do przechwytywania sieci, spójrz na nagrany strumień (TCP) i spróbuj sprawdzić, czy problem jest generowany przez nginx, czy pochodzi (bezpośrednio) z django .

cipy
źródło
0

Miałem bardzo podobny problem, który nękał mnie tak długo, jak miałem tę konfigurację. Podobnie jak ty, używam FastCGI, Nginx i macOS, i znalazłem przypadkowe uszkodzenie w środku dużych żądań (było to około 2% żądań dokumentu 1,5 MB).

Byłem w stanie rozwiązać problem, przełączając się na gniazda Unix przez TCP dla połączenia FastCGI między PHP-FPM (w moim przypadku) a Nginx. Nie wiem, która część układanki jest odpowiedzialna za uszkodzenie, ale uniknięcie wewnętrznego połączenia TCP naprawiło to.

Robert
źródło