WebFaction
Community site: login faq

I am trying to serve static files (HTML + CSS) under Django. (Later on, I will password protect them). However, I am getting the wrong content-type. HTML files are downloaded, not displayed.

My webserver is Apache, and I'm running this under Webfaction. I'm looking at the site under Chromium 18.

I am trying both a naive FileWrapper (sending the file from Django) approach, where I use mimetype to determine the type, and x_modsendfile, where I let the webserver decide.

HTML files are downloaded, not displayed.

Here is what the content header should be, when I serve through my Apache webserver not Django:

HTTP/1.1 200 OK => 
Server => nginx
Date => Wed, 19 Sep 2012 21:51:35 GMT
Content-Type => text/html
Content-Length => 9362
Connection => close
Vary => Accept-Encoding
Last-Modified => Wed, 19 Sep 2012 05:53:00 GMT
ETag => "e3a0c43-2492-4ca079e8fea23"
Accept-Ranges => bytes

Observe that the server claims to be nginx. Webfaction says that for static services it sets up Apache, and indeed I have been configuring an Apache server for Django. But the response says Nginx (!)

Here is the response from the naive FileWrapper implementation, wherein I choose the Content-Type using mimetypes:

HTTP/1.1 200 OK => 
Server => nginx
Date => Wed, 19 Sep 2012 21:53:28 GMT
Content-Type => ('text/html', none)
Content-Length => 9362
Connection => close

The content-type is a TUPLE.

Here is the response from the mod_xsendfile implementation, wherein I don't choose the Content-Type:

HTTP/1.1 200 OK => 
Server => nginx
Date => Wed, 19 Sep 2012 21:52:40 GMT
Content-Type => text/plain
Content-Length => 9362
Connection => close
Vary => Accept-Encoding
Last-Modified => Wed, 19 Sep 2012 05:53:00 GMT
ETag => "e3a0c43-2492-4ca079e8fea23"

Here is my code:

def _serve_file_xsendfile(abs_filename):
    response = django.http.HttpResponse() # 200 OK
    del response['content-type'] # We'll let the web server guess this.
    response['X-Sendfile'] = abs_filename
    return response

def _serve_file_filewrapper(abs_filename):
    p_filename = abs_filename
    if not os.path.exists(p_filename):
        raise Exception('File %s does not exist!')

    try:
        _content_type = mimetypes.guess_type(p_filename)
    except:
        _content_type = 'application/octet-stream'

    print p_filename, _content_type

    wrapper = FileWrapper(file(p_filename))
    response = HttpResponse(wrapper, content_type=_content_type)
    response['Content-Length'] = os.path.getsize(p_filename)
    response['Content-Type'] = _content_type
    return response

def _serve_file(filename):
    abs_filename = _get_absolute_filename(filename)
    return _serve_file_filewrapper(abs_filename)

def public_files(request, filename):
    return _serve_file(filename)

Edit: Here is the urls.py that triggers the above view:

url(r'^(?P<filename>.*)$', 'mysite.views.public_files')

How do I get the correct Content-Type to be served, either for the FileWrapper or mod_xsendfile approach?

asked 19 Sep '12, 16:56

Joseph
134
accept rate: 0%

edited 19 Sep '12, 22:50

Our front-end server listening on port 80 is Nginx, and all of our static-media applications serve content via Nginx. Django applications are essentially a "custom application (listening on port)" which is proxied (via proxy_pass) from the front-end Nginx server to your Apache+mod_wsgi stack which runs Django. This explains why you are seeing the Nginx header.

With respect to the actual code, it's very difficult to debug - can you provide the view you're using, with the full context and lifecycle of the request to response?

(19 Sep '12, 21:36) ryans ♦♦

@ryan: That is my view.py

I have put my url.py at the end of my answer, because the code got messed up in the comment.

What other information do you need?

(19 Sep '12, 22:49) Joseph

We've been looking at this, and it seems that FileWrapper and XsendFile exist in order to serve files as downloads. Your issue is that "HTML files are downloaded, not displayed.", but I think that's exactly what these handlers are trying to do - ensure that the content is presented as a download to the user.

If you're just trying to serve HTML as a web page, then you would use something like the @login_required decorator and serve a standard HttpResponse from the view.

We haven't been able to find any reason why it would be preferable to use FileWrapper or XsendFile to serve a standard page, so I think that the tools are designed this way on purpose in order to make file downloads simple.

(20 Sep '12, 01:57) ryans ♦♦

@ryans:

I have tried what you proposed:

return HttpResponse(open(abs_filename).read())

returns the HTML just fine, but CSS files have content-type text/html.

If I pick the correct content type, because I want to password protect CSS:

return HttpResponse(open(abs_filename).read(), content_type=mimetypes.guess_type(abs_filename))

Then my CSS is not rendered. By the time the HTTP header hits my browser, the content-type has been rewritten as: Content-type: ('text/css', none)

I believe that something wacky is happening between your Apache server and your Nginx server. Maybe not. Regardless, how can I serve CSS through Django on webfaction?

(20 Sep '12, 15:34) Joseph

The issue is defiantly the content type being a tuple, but there is no mechanic in the nginx proxy which could do this, only the python code in the Apache stack someplace could generate a tuple. What happens if you use curl on the server and listen on localhost:port or use an SSH tunnel? Those methods would bypass the front-end nginx server and should provide the same info, this would at least rule out nginx. It could be nginx simply being present, which is confusing the code. If this is the case correct the code so it only sends the fist part of any data it receives something like this,(it may require more logic than this, but this in general)

str(_content_type[1])
(20 Sep '12, 17:52) johns

@johns: Thanks, that fixed it.

(21 Sep '12, 01:00) Joseph
showing 5 of 6 show 1 more comments

_content_type = mimetypes.guess_type(p_filename)

should have been:

_content_type, encoding = mimetypes.guess_type(p_filename)

I'm not sure why I've seen the former code shared.

permanent link

answered 21 Sep '12, 01:00

Joseph
134
accept rate: 0%

Your answer
toggle preview

Follow this question

By Email:

Once you sign in you will be able to subscribe for any updates here

By RSS:

Answers

Answers and Comments

Markdown Basics

  • *italic* or _italic_
  • **bold** or __bold__
  • link:[text](http://url.com/ "title")
  • image?![alt text](/path/img.jpg "title")
  • numbered list: 1. Foo 2. Bar
  • to add a line break simply add two spaces to where you would like the new line to be.
  • basic HTML tags are also supported

Question tags:

×908
×186
×6
×5
×1

question asked: 19 Sep '12, 16:56

question was seen: 5,064 times

last updated: 21 Sep '12, 01:00

WEBFACTION
REACH US
SUPPORT
AFFILIATE PROGRAM
LEGAL
© COPYRIGHT 2003-2019 SWARMA LIMITED - WEBFACTION IS A SERVICE OF SWARMA LIMITED
REGISTERED IN ENGLAND AND WALES 5729350 - VAT REGISTRATION NUMBER 877397162
5TH FLOOR, THE OLD VINYL FACTORY, HAYES, UB3 1HA, UNITED KINGDOM