2

I am building a web API using Django and Nginx that needs to support Byte-Range requests.

Currently, when making a request such as:

curl --header "Content-Type: application/json" 
     --header "Authorization: Token 8f25cd3bb5b43a5277f4237b1d1db0273dbe8f6a" 
     --request POST http://myurl/download/ 
     --header "Range: bytes=0-50" 
     -v

my Django view (the MyFileAPI class) is called, the user is authenticated, and the correct file path (/code/web/myapp/user_id/user_info.pbf) is fetched. However, the entire file is returned instead of only the first 50 bytes.

How do I configure Nginx and Django to allow partial file downloads?

default.conf

server {
    listen                  80;
    server_name             localhost;
    charset                 utf-8;
    client_max_body_size    10M;

    add_header Accept-Ranges bytes;
    proxy_force_ranges on;

    location /static/ {
        alias   /django_static/;
    }

    location / {
        include     uwsgi_params;
        uwsgi_pass  web:8000;
    }
}

nginx.conf

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

views.py

class MyFileAPI(APIView):
    permission_classes = (IsAuthenticated,)

    def post(self, request):
        try:
            user = request.user
            my_file = MyFile.objects.filter(user_id=user).latest("created_date")

            # ex) my_file.filepath = /myapp/13/user_info.pbf
            # ex) my_files_path = /code/web/myapp/13/user_info.pbf
            my_files_path = settings.BASE_DIR + my_file.filepath

            response = FileResponse(open(my_files_path, 'rb'))
            response['Accept-Ranges'] = 'bytes'
            response['X-Accel-Buffering'] = 'no'

            # Adding this next line results in a 404 error
            # response['X-Accel-Redirect'] = my_files_path

            return response

        except Exception as e:
            return Response({'status': str(e)}, content_type="application/json")

When I add the line response['X-Accel-Redirect'] = my_files_path I receive the error The current path /myapp/13/user_info.pbf didn't match any of these. and all of my urls from urls.py are listed.

I know there are other posts on the same topic, but none give a full answer.

Espresso
  • 740
  • 13
  • 32
  • 2
    you are not getting first 51 bytes because you got 404 returned, please paste the error from the `/var/log/nginx/error.log`. If you may for the testing purposes create static file rather than generating it on the Django side, its testing safe unlike what you have right now. – Dusan Gligoric Sep 23 '19 at 14:24
  • may be after fixing the 404 error, you can try some of the answers mentioned here https://stackoverflow.com/questions/22728016/nginx-is-not-accepting-range-of-bytes – opensource-developer Sep 24 '19 at 09:50
  • I have edited my question. Using the code above I can download the entire file without any issues, however when I specify a byte range it is ignored. – Espresso Sep 25 '19 at 16:22
  • Also, the error seems to come from when I include the line `response['X-Accel-Redirect'] = my_files_path` – Espresso Sep 25 '19 at 16:22
  • x-accel-redirect should work this way: your backend server (django) doesn't actually serve the file, it tells nginx to fetch the file directly from another location. In your case, your file should be in a path served directly by nginx, i.e. /static// or, if it's an uploaded file, probably it should be under MEDIA_ROOT and you should add an entry in nginx for MEDIA_URL. Now you're telling nginx to fetch the file from a url processed by Django (which requires a view to handle the request and defeats the purpose of the range header). – dirkgroten Sep 26 '19 at 14:44
  • Django FileResponse class does natively not support Range. See https://stackoverflow.com/questions/14324250/byte-ranges-in-django or https://pypi.org/project/django-ranged-fileresponse/ – user803422 Sep 27 '19 at 15:19
  • `add_header Accept-Ranges bytes; proxy_force_ranges on;` saved my day. Thank you. – Dutch77 Nov 04 '21 at 19:39

0 Answers0