10

I am running Django behind nginx using FastCGI. I have discovered that in some of the responses sent to the client, random data corruption is occurring in the middle of the responses (might be a couple hundred bytes or so in the middle).

At this point I have narrowed it down to either being a bug in either nginx's FastCGI handler or Django's FastCGI handler (i.e. probably a bug in flup), since this problem never occurs when I run the Django server in standalone (i.e. runserver) mode. It only happens in FastCGI mode.

Other interesting trends:

  • It tends to happen on larger responses. When a client logs in for the first time, they are sent a bunch of 1MB chunks to sync them up to the server DB. After that first sync, the responses are much smaller (usually a few KB at a time). The corruption always seems to happen on those 1MB chunks sent at the start.

  • It happens more often when the client is connected to the server via LAN (i.e. low-latency, high-bandwidth connection). This makes me think there is some kind of race condition in nginx or flup that is exacerbated by an increased data rate.

Right now, I've had to work around this by putting an extra SHA1 digest in the response header, and having the client reject responses where the header doesn't match the body checksum, but this is kind of a horrible solution.

Has anyone else experienced anything like this, or have any pointers as to how to identify whether it is flup or nginx that is at fault here so I can file a bug with the appropriate team?

Thanks in advance for any help.

Note: I also posted a similar bug in lighttpd + FastCGI + Django a while back here: https://stackoverflow.com/questions/3714489/lighttpd-fastcgi-django-truncated-response-sent-to-client-due-to-unexpected ... even though this isn't the same thing (truncation vs corruption), it's starting to look like the common culprit is flup / Django rather than the web server ..

Edit: I should also note what my environment is:

  • OSX 10.6.6 on a Mac Mini

  • Python 2.6.1 (System)

  • Django 1.3 (from official tarball)

  • flup 1.0.2 (from Python egg on flup site)

  • nginx +ssl 1.0.0 (from Macports)

EDIT: In response to Jerzyk's comment, the code path that assembles the response looks like (edited for succinctness):

# 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

I don't think it's possible that the Content-Length is wrong based on that, and AFAIK there is no way to mark a Django HttpResponse object as explicitly binary as opposed to text. Also, since the issue happens only intermittently, I don't think that explains it otherwise presumably you would see it on every request.

EDIT @ionelmc: You have to set the Content-Length in Django - nginx does not set this for you, as per the below example once I disabled setting Content-Length explicitly:

$ 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
9
  • If the initial chunks do not change often or are not user specific maybe writing to disk and serving directly via nginx is a better way?
    – sunn0
    Oct 22, 2010 at 21:32
  • Unfortunately, the chunks are both user-specific and frequently changing, so no caching of that kind would be appropriate for this application. I'm also keen to find out what is actually causing this data corruption rather than just working around it (which I am already doing with the extra SHA1 digest in the header).
    – glenc
    Oct 23, 2010 at 4:02
  • I can think of two possible reasons: wrong encoding - HttpRespose as text vs. binary or wrong headers (especially content-length)
    – Jerzyk
    Apr 2, 2011 at 17:50
  • 1
    @glenc what is a content-type for this response? if this is binary - can you try to set it? (e.g. mimetype='application/x-ms-excel' or else)
    – Jerzyk
    Apr 4, 2011 at 19:32
  • 2
    You don't need to set the content-length if your Transfer-Encoding is chunked. rfc 2616 explicitly forbids this: "The Content-Length header field MUST NOT be sent if these two lengths are different (i.e., if a Transfer-Encoding header field is present)."
    – ionelmc
    Jun 23, 2011 at 14:01

4 Answers 4

1

Do you have any kind of nginx caching ( bypass / no_cache ) directive active for the fastcgi responses?

In nginx' 1.0.3 Changenotes they fixed a response corruption:

Bugfix: a cached response may be broken if "proxy/fastcgi/scgi/ uwsgi_cache_bypass" and "proxy/fastcgi/scgi/uwsgi_no_cache" directive values were different; the bug had appeared in 0.8.46.

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

0

Perhaps the occasional corruption only happens if the output contains at least one UTF-8 character.

Content-length and string length are not the same thing, because one UTF-8 character can contain 2 to 5 bytes.

1
  • Hmmmm .. while this is true it doesn't seem likely to be the cause because the corruption was happening in the middle of the data chunks and was not simply a case of missing data at the end.
    – glenc
    Oct 12, 2011 at 22:51
0

One way to troubleshoot this case a bit more would be to:

  • have nginx and django running on different hardware (so that you can easily capture the traffic)
  • capture the traffic from the client to --/-> nginx and nginx --/-> django (i.e. use wireshark)

Once you detect an error in the client side (based on the sha1), go to the network capture, look into the recorded (TCP) stream and try to find whether the problem is generated by nginx or does it come (directly) from django.

0

I had a very similar issue which was plaguing me for as long as I've had this setup. Like you, I use FastCGI, Nginx and macOS, and found random corruption in the middle of large requests (it was about 2% of requests of a 1.5 MB document).

I was able to solve my problem by switching to Unix sockets over TCP for the FastCGI connection between PHP-FPM (in my case) and Nginx. I don't know which piece of the puzzle is responsible for the corruption, but avoiding the internal TCP connection did fix it.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .