Summary Process

  1. Check your proxy settings
  2. Add proper settings to Django settings.py file.

Introduction

I am a huge fan of APIs, and when I came around Django Rest Framework (DRF), I was very excited and implemented a simple api withing a very short time. However, given it contains a lot of magic, I had no idea what to do when stuff worked well in localhost but then failed in production server.

My Hosting Set Up

In the production server, I use a PostgreSQL database, gunicorn server and Caddy as proxy and static files server. I have a simple docker-compose config that ties all these together for easy deployment whenever I push to Github.

The Problem

In localhost, I get proper urls from DRF, that is, http://localhost:8000/api/v1/collection/item_id/. In the production server, I get the hostname set as the docker-compose name given in the config. That means that DRF assumes that I there exists only the docker network and thus the urls look like: http://web:8000/api/v1/collection/item_id/ instead of https://example.com/api/v1/collection/item_id/.

This problem made me google for how to set up correct hostname in django settings, how to return correct urls in hyperlinked model serializers DRF and so many other stuff. However, it looked like my issue was unique.

First, most people apparently don’t use Caddy server, they use either Apache or Nginx. This got me so frustrated, as I am used to finding solutions to such problems.

I experimented with passing along some headers, tweaking the serializers and so on with no success.

The Solution

So, I had this snippet in the caddyfile to proxy to the gunicorn server:


example.com {
    bind ${ADDRESS}
    proxy / web:8000
    tls info@example.com
}

Then, while reading about forward and reverse proxies, something came up: whenever you have a proxy, the proxy will, by default, strip some headers. Therefore, when the request would get to my DRF backend, it would see as if it is coming from gunicorn, yet it is coming from the domain.

So, I did a very small tweak:


example.com {
    bind {$ADDRESS}
    proxy / web:8000 {
        transparent
    }
    tls info@example.com
}

Believe it or not, this worked magic. This made Django Rest Framework respond with the correct url for all the resources in the backend. However, there was still one small challenge. It was returning http links instead of https links and this made the frontend PATCH requests to the server to fail because, as much as the backend was returning the correct headers, the preflight requests were being redirected back to the https one, and so posting to http that comes witht the resources would fail.

To fix this, I added this small line in the settings:

SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

Note: This line should only be added if you are actually using HTTPS, and are behind a proxy. The proxy will in most cases connect to your backend insecurely, that is, via HTTP and hence your backend server will think you are using the HTTP protocol yet you are actually using HTTPS.

Also, just so cors do not bring a challenge, I added these headers to the proxy:


example.com {
    bind {$ADDRESS}
    proxy / web:8000 {
        transparent
        header_downstream Access-Control-Allow-Origin "*"
        header_downstream -Server
        header_downstream Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE, PUT, PATCH"
    }
    tls info@example.com
}

I added these headers since even after adding the gjango-cors-headers package correctly, somehow, the proxy was stripping some of those headers and so I had to add manually. The header_downstream -Server line is vital. It strips the server details.

Conclusion

Django Rest Framework (DRF) is a great framework. You can create a whole API within a day. However, whenever you encounter an issue, the solution always lies within your configurations. In my case, it was just the proxy making things hard.


comments powered by Disqus

You might also like