Archive pour la catégorie ‘apache’

Serving static files with Apache while controlling access with Django

Dimanche 13 mars 2011

Theses days, I’ve added the ability to upload files in Aemanager for Ma Petite Auto Entreprise. Since a user may only access files he owns, a solution could have been putting them into an obfuscated directory (with a uid) in the media directory but I didn’t want to rely on such a solution which I consider not enough secure. I could have then let Apache serve theses files.

An other way to achieve this goal could be putting files in a directory not accessible by http, then in a view, read the file and stream the content with Django. It matches my goal but it isn’t efficient nor recommended to serve static files through Django.

The last solution, the better, is to use mod_xsendfile, a module for Apache. The user ask for a file represented by an arbitrary url and Django just sends a response without content, setting X-Sendfile header to tell Apache where it should read the content.

First, you need to install mod_xsendfile following these instructions :

$ sudo apxs2 -cia mod_xsendfile.c

Restart Apache when finished.

Here is a view of Aemanager which provide protected static files:

@settings_required
@subscription_required
def contract_uploaded_contract_download(request, id):
    contract = get_object_or_404(Contract, pk=id, owner=request.user)
    response = HttpResponse(mimetype='application/force-download')
    response['Content-Disposition'] = 'attachment;filename="%s"'\
                                    % smart_str(contract.contract_file.name)
    response["X-Sendfile"] = "%s%s" % (settings.FILE_UPLOAD_DIR, 
                                       contract.contract_file.name)
    response['Content-length'] = contract.contract_file.size

    return response

Here the important thing is the X-Sendfile header.

However, there’s also a problem with files which have non-ascii characters in their name. The best solution I’ve found is to use:

unicodedata.normalize('NFKD', filename).encode('ascii', 'ignore')

This replaces characters such as « é » by their ascii equivalent, « e » in this case.

Here is my model to let you see how it’s done :

store = FileSystemStorage(location=settings.FILE_UPLOAD_DIR)

def contract_upload_to_handler(instance, filename):
    return "%s/contract/%s" % (instance.owner.username,
                               unicodedata.normalize('NFKD',
                                                     filename).encode('ascii',
                                                                      'ignore'))

class Contract(OwnedObject):
    customer = models.ForeignKey(Contact,
                                 verbose_name=_('Customer'),
                                 related_name="contracts")
    title = models.CharField(max_length=255,
                             verbose_name=_('Title'))
    contract_file = models.FileField(upload_to=contract_upload_to_handler,
                                     null=True,
                                     blank=True,
                                     storage=store,
                                     verbose_name=_('Uploaded contract'))
    content = models.TextField(verbose_name=_('Content'),
                               null=True,
                               blank=True)
    update_date = models.DateField(verbose_name=_('Update date'),
                                   help_text=_('format: mm/dd/yyyy'))

EDIT : The Xsendfile module also exists for nginx and charset issue in filename can be solved with header X-Accel-Charset: utf-8

Apache default NameVirtualHost pitfall

Mardi 22 février 2011

I was wondering why when I type a domain or subdomain without virtualhost behind I come on my first virtualhost and not the default one.

I tried to rename the default into 000-default without solving the issue. My NameVirtualHost is defined with my server ip :

NameVirtualHost my_ip:80

and I tried

<VirtualHost _default_:80>

or

<VirtualHost *:80>

and at the beginning I forgot to add the ServerName in the default vhost. But none of these worked.

The solution is only to defined your vhost with your server ip :

<VirtualHost my_ip:80>
    ServerName myserver.tld
    ...

And do not forget to set your ssl certificate in your default ssl vhost since it will be the one sent during ssl negociation.

Finally solved ssl_error_rx_record_too_long

Mercredi 28 avril 2010

I’ll write in english for this article since it’s technical and may be very usefull for many people.

So I had this error for days on all my apache ssl virtualhosts. I guess it came from the last dist-upgrade I’ve made. I’ve googled for hours without finding any working solution. Everything was working well before and I’ve made no related changes.

Listen 443 was set in ports.conf, I removed the « if » statement to be sure, as in mods-enabled/ssl.conf. I checked that I’ve had no virtualhost without port 80 or 443 defined. I’ve checked that there’s no default virtual host with *.

I also tried to add my ip in Listen statement and « https » at the end.

SSLEngine was set to « On », SSLCertificateFile .cert file was defined and ok, so as SSLCertificateKeyFile (I was pretty sure since everything was ok before).

Finally, I’ve disabled all my ssl vhosts, just keeping the one I wanted for the moment, and it worked. After enabling the others one by one, I found the bad one : in one vhost, instead of doing :

<VirtualHost ip:80>
  ServerName domain.tld
  ServerAlias domain.tld
  redirect / https://domain.tld
</VirtualHost>

I was doing the opposite (I guess I didn’t want any SSL on this site) :

<VirtualHost ip:443>
  ServerName domain.tld
  ServerAlias domain.tld
  redirect / http://domain.tld
</VirtualHost>

I don’t know why, but after removing this part, everything was back to normal. It was not really important for me, but if you have any explaination, I’ll be glad to hear it.

I hope this could help someone ;-)

EDIT : adding

SSLEngine On
SSLCertificateFile    /path/to.cert
SSLCertificateKeyFile /path/to.key

in my ssl vhost redirecting to non-ssl vhost solved the problem too (it seems logical, but it was working without before).