Django ミドルウェア完全ガイド 2026:カスタムミドルウェア、ロギング実装と面接対策

Django 6.0のミドルウェアを徹底解説。カスタムミドルウェアの作成方法、非同期ミドルウェア、リクエストIDロギング、そして技術面接で頻出の質問と回答を詳しく説明します。

Django Advanced Middleware 2026

Djangoミドルウェアは、すべてのリクエスト・レスポンスサイクルの中核を担っています。しかし、多くの開発者はミドルウェアをブラックボックスとして扱いがちです。ミドルウェアの実行順序、カスタムミドルウェアの作成タイミング、ミドルウェアレベルでのロギング実装を理解することは、技術面接においてシニアDjango開発者とジュニア開発者を区別する重要なポイントとなります。

Django 6.0 ミドルウェア

Django 6.0では、ネイティブのasync/await構文による非同期ミドルウェアが完全にサポートされています。ミドルウェアクラスにasync_capable = Truesync_capable = Falseを設定することで、スレッドオーバーヘッドなしでリクエストを処理できます。

Djangoミドルウェアのリクエスト・レスポンスサイクルにおける実行順序

Djangoのミドルウェアは「オニオン(玉ねぎ)モデル」に従って動作します。リクエストが到着すると、MIDDLEWAREリストの上から下へ各ミドルウェアを通過します。ビューがレスポンスを返すと、レスポンスは下から上へミドルウェアを逆順に通過します。この順序は重要であり、セッションデータが必要なミドルウェアはSessionMiddlewareの後に配置する必要があります。

Django 6.0のデフォルトMIDDLEWARE設定は以下の通りです:

python
# settings.py
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

各ミドルウェアは、get_response()を呼び出す代わりにHttpResponseを直接返すことで、チェーンをショートサーキット(短絡)できます。この場合、内側のミドルウェアとビューは実行されませんが、外側のミドルウェアは引き続きレスポンスを処理します。

Django 6.0でのクラスベースカスタムミドルウェアの作成

現代のミドルウェアパターンでは、__init____call__メソッドを持つクラスが必要です。__init__メソッドはサーバー起動時に一度だけ実行され、__call__はリクエストごとに実行されます。

python
# middleware/timing.py
import time
import logging

logger = logging.getLogger(__name__)

class RequestTimingMiddleware:
    """Logs the time taken to process each request."""

    def __init__(self, get_response):
        # Called once when the server starts
        self.get_response = get_response

    def __call__(self, request):
        # Code before the view executes
        start_time = time.perf_counter()

        # Call the next middleware or view
        response = self.get_response(request)

        # Code after the view executes
        duration_ms = (time.perf_counter() - start_time) * 1000
        logger.info(
            "Request %s %s completed in %.2fms",
            request.method,
            request.path,
            duration_ms
        )

        return response

ミドルウェアをsettings.pyに登録するには、インポートパスをMIDDLEWAREに追加します。配置位置は重要であり、タイミングミドルウェアはリクエストライフサイクル全体を計測するためにリストの早い位置に配置します。

高並行性Djangoアプリケーション向け非同期ミドルウェア

Django 6.0は非同期リクエストをネイティブに処理します。ASGIで非同期ビューを実行するアプリケーションでは、同期ミドルウェアがパフォーマンスボトルネックを引き起こします。これはDjangoが同期ミドルウェアをスレッドプールエグゼキュータでラップするためです。非同期ミドルウェアを作成することで、このオーバーヘッドを排除できます。

python
# middleware/async_timing.py
import time
import logging
from asgiref.sync import iscoroutinefunction, markcoroutinefunction

logger = logging.getLogger(__name__)

class AsyncRequestTimingMiddleware:
    """Async-native timing middleware for ASGI deployments."""

    async_capable = True
    sync_capable = False

    def __init__(self, get_response):
        self.get_response = get_response
        if iscoroutinefunction(self.get_response):
            markcoroutinefunction(self)

    async def __call__(self, request):
        start_time = time.perf_counter()

        # Await the next middleware or async view
        response = await self.get_response(request)

        duration_ms = (time.perf_counter() - start_time) * 1000
        logger.info(
            "Async request %s %s completed in %.2fms",
            request.method,
            request.path,
            duration_ms
        )

        return response

django.utils.decorators@sync_and_async_middlewareデコレータは、ランタイムでiscoroutinefunction(get_response)をチェックすることで、同期と非同期の両方のリクエストを処理するミドルウェアを作成します。

リクエストIDロギングミドルウェアの実装

分散システム間でログを相関させるには、リクエストごとに一意の識別子が必要です。このパターンはDjango面接質問で頻繁に登場します。ミドルウェア、ロギング、本番システムのデバッグに関する理解を示すためです。

python
# middleware/request_id.py
import uuid
import logging

class RequestIDMiddleware:
    """Attaches a unique request ID to every request for log correlation."""

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

    def __call__(self, request):
        # Generate or extract request ID from header
        request_id = request.headers.get("X-Request-ID")
        if not request_id:
            request_id = str(uuid.uuid4())

        # Attach to request object for access in views
        request.request_id = request_id

        response = self.get_response(request)

        # Add request ID to response headers
        response["X-Request-ID"] = request_id

        return response

すべてのログメッセージにrequest_idを含めるには、カスタムロギングフィルターを作成します:

python
# logging_filters.py
import logging

class RequestIDFilter(logging.Filter):
    """Injects request_id into log records."""

    def filter(self, record):
        from django.middleware.request_id import local
        record.request_id = getattr(local, "request_id", "no-request-id")
        return True

settings.pyでフィルターを設定します:

python
# settings.py
LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "filters": {
        "request_id": {
            "()": "logging_filters.RequestIDFilter",
        },
    },
    "formatters": {
        "verbose": {
            "format": "[{levelname}] {asctime} [{request_id}] {name}: {message}",
            "style": "{",
        },
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "verbose",
            "filters": ["request_id"],
        },
    },
    "root": {
        "handlers": ["console"],
        "level": "INFO",
    },
}

Djangoの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

きめ細かな制御のためのミドルウェアフックメソッド

__call__以外にも、Djangoミドルウェアは3つのフックメソッドをサポートしています:process_viewprocess_exceptionprocess_template_response。これらはリクエストライフサイクルの特定のポイントで制御を提供します。

実行前ロジック用のprocess_view

python
# middleware/permission_check.py
from django.http import HttpResponseForbidden

class PermissionCheckMiddleware:
    """Checks permissions before view execution."""

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

    def __call__(self, request):
        return self.get_response(request)

    def process_view(self, request, view_func, view_args, view_kwargs):
        # Access view metadata
        required_permission = getattr(view_func, "required_permission", None)

        if required_permission:
            if not request.user.has_perm(required_permission):
                return HttpResponseForbidden("Permission denied")

        # Return None to continue to the view
        return None

このパターンにより、ビューにパーミッション要件をデコレートできます:

python
# views.py
def require_permission(perm):
    def decorator(view_func):
        view_func.required_permission = perm
        return view_func
    return decorator

@require_permission("app.can_edit")
def edit_resource(request, pk):
    # View logic
    pass

一元化されたエラーハンドリング用のprocess_exception

python
# middleware/error_tracking.py
import logging
import traceback

logger = logging.getLogger(__name__)

class ErrorTrackingMiddleware:
    """Logs exceptions with full context before Django handles them."""

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

    def __call__(self, request):
        return self.get_response(request)

    def process_exception(self, request, exception):
        logger.error(
            "Unhandled exception in %s %s: %s\n%s",
            request.method,
            request.path,
            str(exception),
            traceback.format_exc(),
            extra={
                "user_id": getattr(request.user, "id", None),
                "request_id": getattr(request, "request_id", None),
            }
        )
        # Return None to let Django handle the exception
        return None

ミドルウェアのprocess_exceptionメソッドは逆順で実行されます。いずれかがHttpResponseを返すと、リストの前方にあるミドルウェアは例外を見ることができません。

Django 6.0セキュリティミドルウェアのアップデート

Django 6.0ではUpdateCacheMiddlewareにいくつかのセキュリティ修正が含まれています。2026年のDjangoセキュリティリリースによると、大文字小文字混在のCache-Control: privateを持つレスポンスが誤ってキャッシュされ、Authorizationヘッダーを持つリクエストへのレスポンスが共有キャッシュに保存される可能性がありました。

Django 5.1で導入されたLoginRequiredMiddlewareは、サイト全体で認証を強制します:

python
# settings.py
MIDDLEWARE = [
    # ... other middleware
    "django.contrib.auth.middleware.LoginRequiredMiddleware",
]

# Exempt specific views
from django.contrib.auth.decorators import login_not_required

@login_not_required
def public_view(request):
    return HttpResponse("Public content")

これは保護されたビューに@login_requiredをデコレートする従来のパターンを逆転させます。ほとんどのビューで認証が必要なアプリケーションでは、ボイラープレートを削減し、保護されたエンドポイントの意図しない公開を防ぎます。

MiddlewareMixinとレガシーミドルウェアからの移行

MiddlewareMixinは、旧スタイルのミドルウェア(process_requestprocess_responseメソッドを使用)を新しい呼び出し可能パターンに橋渡しします。django.utils.deprecation.MiddlewareMixinは非推奨であり、Django 6.2で削除される予定です。代わりにdjango.middleware.MiddlewareMixinを使用してください。

python
# middleware/legacy_style.py
from django.middleware import MiddlewareMixin

class LegacyStyleMiddleware(MiddlewareMixin):
    """Middleware using the legacy hook-based pattern."""

    def process_request(self, request):
        # Called before view, return None to continue
        request.custom_attribute = "value"
        return None

    def process_response(self, request, response):
        # Called after view, must return response
        response["X-Custom-Header"] = "middleware-added"
        return response

新しいミドルウェアではMiddlewareMixinを避けてください。呼び出し可能パターンはより明確な制御フローを提供し、非同期Djangoとの互換性も向上します。

Django ミドルウェアの面接でよく聞かれる質問

技術面接では、Djangoのリクエストライフサイクルの理解を評価するためにミドルウェアの質問がよく含まれます。これらの質問はシニアDjango開発者の面接で頻繁に登場します。

Q: ミドルウェアの順序とレスポンスの実行順序の違いは何ですか?

ミドルウェアはMIDDLEWAREを通じてリクエストを上から下へ処理しますが、レスポンスは下から上へ処理します。玉ねぎのように考えてください:最も外側のミドルウェアがリクエストを最初に見て、レスポンスを最後に見ます。

Q: ミドルウェアのショートサーキットはどのように機能しますか?

ミドルウェアがget_response()を呼び出す代わりにHttpResponseを返すと、リクエストは内側のミドルウェアやビューに到達しません。レスポンスは戻る途中ですべての外側のミドルウェアを通過します。この動作は、すべてのprocess_responseメソッドがショートサーキットに関係なく実行される旧来のMIDDLEWARE_CLASSES設定とは異なります。

Q: ミドルウェアでrequest.userにアクセスすべきタイミングは?

AuthenticationMiddlewareが実行された後のみです。認証ミドルウェアがアタッチする前にrequest.userにアクセスするとAttributeErrorが発生します。ユーザー情報が必要なカスタムミドルウェアは、MIDDLEWAREリストでAuthenticationMiddlewareの後に配置してください。

Q: ミドルウェアを非同期互換にするにはどうすればよいですか?

ミドルウェアクラスにasync_capable = Trueを設定し、await get_response(request)を使用して__call__を非同期メソッドとして実装します。同期と非同期の両方のコンテキストで動作する必要があるミドルウェアには、@sync_and_async_middlewareを使用し、iscoroutinefunction(get_response)をチェックして適切に分岐します。

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

本番環境と面接に向けたDjangoミドルウェアのマスター

Djangoミドルウェアは、リクエストライフサイクル全体にまたがる横断的関心事を制御します。Django 6.0でミドルウェアを扱う際の重要なポイントは以下の通りです:

  • ミドルウェアはオニオンパターンで実行される:リクエストは上から下へ、レスポンスは下から上へ流れる
  • ASGIデプロイメントではasync_capable = Trueを設定してスレッドプールのオーバーヘッドを回避する
  • process_viewは実行前のビューメタデータ検査に、process_exceptionは一元化されたエラーロギングに使用する
  • リクエストIDミドルウェアは分散サービス間のログ相関を可能にし、本番環境での一般的な要件である
  • LoginRequiredMiddlewareは認証パターンを逆転させる:ビューはデフォルトで保護され、公開ビューは明示的に除外される
  • 非推奨のdjango.utils.deprecation.MiddlewareMixinはDjango 6.2でdjango.middleware.MiddlewareMixinに移行される
  • ミドルウェアの順序は、どのコンポーネントがrequest.user、セッションデータ、その他のリクエスト属性を見るかを決定する

Django ORMとREST frameworkパターンのより詳細な解説については、Django ORMクエリ最適化ガイドを参照してください。

今日のチャレンジ

Django のバグを見つけられますか

実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

Anthony Fillion-Maillet

執筆

Anthony Fillion-Maillet

SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年9月18日 更新

タグ

#django
#middleware
#python
#logging
#interview

共有

関連記事