Plain is headed towards 1.0! Subscribe for development updates →

HTTP

HTTP request and response handling.

Overview

Typically you will interact with Request and Response objects in your views and middleware.

from plain.views import View
from plain.http import Response

class ExampleView(View):
    def get(self):
        # Accessing a request header
        print(self.request.headers.get("Example-Header"))

        # Accessing a query parameter
        print(self.request.query_params.get("example"))

        # Creating a response
        response = Response("Hello, world!", status_code=200)

        # Setting a response header
        response.headers["Example-Header"] = "Example Value"

        return response

Content Security Policy (CSP)

Plain includes built-in support for Content Security Policy (CSP) through nonces, allowing you to use strict CSP policies without 'unsafe-inline'.

Each request generates a unique cryptographically secure nonce available via request.csp_nonce:

Configuring CSP Headers

Include CSP in DEFAULT_RESPONSE_HEADERS using {request.csp_nonce} placeholders for dynamic nonces:

# app/settings.py
DEFAULT_RESPONSE_HEADERS = {
    "Content-Security-Policy": (
        "default-src 'self'; "
        "script-src 'self' 'nonce-{request.csp_nonce}'; "
        "style-src 'self' 'nonce-{request.csp_nonce}'; "
        "img-src 'self' data:; "
        "font-src 'self'; "
        "connect-src 'self'; "
        "frame-ancestors 'self'; "
        "base-uri 'self'; "
        "form-action 'self'"
    ),
    # Other default headers...
    "X-Frame-Options": "DENY",
}

The {request.csp_nonce} placeholder will be replaced with a unique nonce for each request.

Use tools like Google's CSP Evaluator to analyze your CSP policy and identify potential security issues or misconfigurations.

Using Nonces in Templates

Add the nonce attribute to inline scripts and styles in your templates:

<!-- Inline script with nonce -->
<script nonce="{{ request.csp_nonce }}">
    console.log("This script is allowed by CSP");
</script>

<!-- Inline style with nonce -->
<style nonce="{{ request.csp_nonce }}">
    .example { color: red; }
</style>

External scripts and stylesheets loaded from 'self' don't need nonces:

<!-- External scripts/styles work with 'self' directive -->
<script src="/assets/app.js"></script>
<link rel="stylesheet" href="/assets/app.css">

Customizing Default Response Headers

Plain applies default response headers to all responses via DEFAULT_RESPONSE_HEADERS in settings. Views can customize these headers in several ways:

Override Default Headers

Set the header to a different value in your view:

class MyView(View):
    def get(self):
        response = Response("content")
        # Override the default X-Frame-Options: DENY
        response.headers["X-Frame-Options"] = "SAMEORIGIN"
        return response

Remove Default Headers

Set the header to None to prevent it from being applied:

class EmbeddableView(View):
    def get(self):
        response = Response("content")
        # Remove X-Frame-Options entirely to allow embedding
        response.headers["X-Frame-Options"] = None
        return response

Extend Default Headers

Read the default value from settings, modify it, then set it in your view:

from plain.runtime import settings

class MyView(View):
    def get(self):
        response = Response("content")

        # Get the default CSP policy
        if csp := settings.DEFAULT_RESPONSE_HEADERS.get("Content-Security-Policy"):
            # Format it with the current request to resolve placeholders
            csp = csp.format(request=self.request)
            # Extend with additional sources
            response.headers["Content-Security-Policy"] = (
                f"{csp}; script-src https://analytics.example.com"
            )

        return response