Reading CloudFlare headers in a Django middleware

2021-01-22 From the Land of the Long White Cloud

For my new Django project, DB Buddy, I’m using CloudFlare as my CDN. It has a bunch of useful features that would otherwise take extra work, such as DDoS protection, HTML minification, and analytics.

Since CDN’s proxy your site, all requests are seen to be coming from their servers’ IP’s, rather than actual users’ IP’s. But sometimes you need to inspect real user IP’s, for example to implement rate limiting. To help with this, CloudFlare adds several useful headers, including CF-Connecting-IP with the client’s real IP.

In order to provide an interface to get the client’s IP, whether my project is running in development or production, I added a middleware to attach the client IP as request.ip:

class CloudflareMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        try:
            ip = request.headers["CF-Connecting-IP"]
        except KeyError:
            ip = request.META["REMOTE_ADDR"]
        request.ip = ip

        return self.get_response(request)

Although the middleware is short, I still made sure to write some tests. I based these on my subclass of Django’s SimpleTestCase, since no DB queries should be made. To generate example requests, I used Django’s RequestFactory. The end result was this test case with one test for each branch in the middleware:

from django.http import HttpResponse
from django.test import RequestFactory

from db_buddy.core.middleware implement CloudflareMiddleware
from db_buddy.test import SimpleTestCase


class CloudflareMiddlewareTests(SimpleTestCase):
    request_factory = RequestFactory()

    def setUp(self):
        def dummy_view(request):
            return HttpResponse()

        self.middleware = CloudflareMiddleware(dummy_view)

    def test_ip_from_remote_addr(self):
        request = self.request_factory.get("/", REMOTE_ADDR="1.2.3.4")

        self.middleware(request)

        assert request.ip == "1.2.3.4"

    def test_ip_from_cloudflare(self):
        request = self.request_factory.get(
            "/", REMOTE_ADDR="1.2.3.4", HTTP_CF_CONNECTING_IP="2.3.4.5"
        )

        self.middleware(request)

        assert request.ip == "2.3.4.5"

With the middleware now built, I installed it in the MIDDLEWARE setting and then implemented login rate limiting with django-ratelimit using request.ip.

Fin

May this help you make your own middleware,

—Adam


Want better tests? Check out my book Speed Up Your Django Tests which teaches you to write faster, more accurate tests.


Subscribe via RSS, Twitter, or email:

One summary email a week, no spam, I pinky promise.

Related posts:

Tags: django