# CloudFront → ALB → Django 構成で API レスポンスの URL スキームが http:// になる問題と解決策

CloudFront → ALB → Django 構成で API レスポンスの URL スキームが http:// になる問題と解決策 はじめに AWS の CloudFront + ALB + ECS Fargate で Django REST Framework (DRF) の API サーバーを運用していたところ、API レスポンスに含まれる URL が http:// で返されるという問題に遭遇しました。本記事では原因の調査過程と、最終的な解決策を紹介します。 構成 Client (HTTPS) ↓ CloudFront (SSL終端, us-east-1) ↓ HTTP ALB (HTTP:80のみ受付, ap-northeast-1) ↓ HTTP ECS Fargate (Gunicorn + Uvicorn, port 9000) ↓ Django REST Framework CloudFront がSSLを終端し、ALB へは HTTP で転送する構成です。 問題 DRF の API ルート (/api/rest/) にアクセスすると、レスポンスに含まれる URL がすべて http:// になっていました。 ...

2026年2月24日 · 2 分

CloudWatch Logs のエラーを自動で GitHub Issues に課題化する

CloudWatch Logs のエラーを自動で GitHub Issues に課題化する ECS で稼働するWebアプリケーションのエラーログを自動的に GitHub Issues に報告する仕組みを構築しました。手動でログを監視する必要がなくなり、エラー発生時に即座にチームが認識・対応できるようになります。 背景 マルチテナントの業務システムを ECS Fargate 上で運用しています。アプリケーションは2つあり、それぞれ異なるフレームワークで構築されています。 アプリ フレームワーク 用途 web Laravel (PHP) 業務管理システム api Django (Python) API サーバー これまで CloudWatch Logs にログは収集していたものの、エラーの検知は手動確認に頼っていました。500エラーや例外発生を見逃すリスクがあり、自動検知の仕組みが必要でした。 アーキテクチャ Subscription Filter + Lambda + GitHub Issues API の構成を採用しました。 CloudWatch Logs (/ecs/{prefix}-ecs-{app}) └── Subscription Filter (エラーパターンマッチ) └── Lambda Function (Docker/arm64, Python 3.12) ├── エラー解析 (HTTP 5xx, 例外, スタックトレース) ├── ±5秒のログコンテキスト取得 ├── 既存 Open Issue 検索 └── 新規 Issue 作成 or 既存 Issue にコメント追加 この構成を選んだ理由 方式 リアルタイム性 柔軟性 コスト Subscription Filter + Lambda (採用) 高 高 中 Metric Filter + Alarm + SNS 中 (1分以上遅延) 低 低 CloudWatch Logs Insights (定期実行) 低 高 低 Subscription Filter はログ出力時にほぼリアルタイムで Lambda を起動するため、エラー発生から数秒で Issue が作成されます。 ...

2026年2月24日 · 4 分

django-oauth-toolkit 2.0 の client_secret ハッシュ化で外部連携が壊れた話

django-oauth-toolkit 2.0 の client_secret ハッシュ化で外部連携が壊れた話 TL;DR django-oauth-toolkit を 1.x から 2.0 にアップグレードすると、Application.client_secret が 平文からハッシュ値に自動変換 される。この変更に気づかず、DB 上のハッシュ値を「シークレット」として外部サービスにコピーすると、二重ハッシュ で認証が通らなくなる。さらに、Application を動的に生成するコードがある場合、バージョンアップ後に平文を返すべき箇所でハッシュ値を返してしまう問題も起きる。 背景 2つの Django サービス間で OAuth2 Client Credentials Grant による認証を行っていた。 サービス 役割 django-oauth-toolkit Service A (リソースサーバー) ファイル配信 API を提供 2.4.0 Service B (クライアント) API からファイルを取得 1.7.1 Service B は Service A の OAuth2 トークンエンドポイントに HTTP Basic Auth で client_id:client_secret を送信し、アクセストークンを取得してからファイルをダウンロードする。 Service B Service A | | |-- POST /o/token/ --------------->| | Authorization: Basic base64( | | client_id:client_secret) | | |-- client_secret をハッシュ化 | |-- DB のハッシュ値と比較 |<-- access_token -----------------| | | |-- GET /api/files/ -------------->| | Authorization: Bearer token | |<-- file data --------------------| このフローは数年間安定稼働していた。 ...

2026年2月13日 · 5 分

Django Email

メール送信 Djangoでメールを送信する際に、都度サーバーを切り替える方法はいくつかあります。以下の手順で実装できます。 メールサーバーの設定を動的に変更する: DjangoのEmailMessageクラスを使用して、メール送信時にサーバー設定を動的に変更できます。例えば、以下のようにconnectionパラメータを使用して異なるサーバーを指定します。 1 2 3 4 5 6 7 8 9 10 11 12 13 from django.core.mail import EmailMessage, get_connection def send_email(subject, message, from_email, recipient_list, server_settings): connection = get_connection( host=server_settings['EMAIL_HOST'], port=server_settings['EMAIL_PORT'], username=server_settings['EMAIL_HOST_USER'], password=server_settings['EMAIL_HOST_PASSWORD'], use_tls=server_settings['EMAIL_USE_TLS'], use_ssl=server_settings['EMAIL_USE_SSL'], ) email = EmailMessage(subject, message, from_email, recipient_list, connection=connection) email.send() サーバー設定のリストを用意する: 複数のサーバー設定をリストで管理し、メール送信時にランダムまたは順番に選択する方法です。 ...

2024年11月28日 · 1 分

Strawberry

Strawberry https://strawberry.rocks/ Djanog Integration https://strawberry.rocks/docs/integrations/django Getting started with Strawberry 1 2 3 uv add strawberry-graphql[debug-server] uv add strawberry-graphql-django uv add django-choices-field\n strawberry-sqlalchemy https://github.com/strawberry-graphql/strawberry-sqlalchemy 記事 FastAPI と Strawberry(GraphQL) が動作する諸々整ったデモアプリを作ってみた https://github.com/0machi/FastAPI_Strawberry_strawberry-sqlalchemy_DemoApp Graphene vs Strawberry: Which is better for providing a GraphQL API? Django Developing GraphQL APIs in Django with Strawberry

2024年8月1日 · 1 分

FastAPI

FastAPI FastAPI入門 FastAPI を用いた API 開発テンプレート 【FastAPI】Uvicorn と Gunicorn、WSGI と ASGI、ワーカープロセスについて実施コマンドと共に解説 Hypercorn+FastAPI のコンテナイメージを作成してみる Hypercorn Django を Hypercorn とともに使う How to deploy any Python Web Application? FastAPI を始める時のメモ(ボツ) How to serve HTTP 1, 2, and 3 in Python 1 poetry add fastapi hypercorn quart filters fastapi-filters: https://pypi.org/project/fastapi-filters/ https://fastapi-filter.netlify.app/ https://github.com/arthurio/fastapi-filter/tree/main その他: https://pypi.org/project/fastapi-query-tools/ モデルマッパー SQLModel(https://sqlmodel.tiangolo.com/) Database Migrations with sqlmodel and alembic FastAPI + SQLModel を使った簡単API開発をやってみた Pythonライブラリ(SQL):SQLModel(応用編-FastAPI) Pythonライブラリ(SQL):SQLModel Alembicでマイグレーション(FastAPI+SQLModel) FastAPI SQLModel 入門 https://github.com/Joichiro433/Blog-fastapi-todo/tree/main

2024年6月24日 · 1 分

OAuth: PKCE

OAuth PKCE Authorization Code with PKCE on Django using django-oauth-toolkit PKCE_REQUIRED RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients 日本語訳

2024年2月28日 · 1 分

Django: Push Notification

Django: Push Notification https://github.com/jazzband/django-push-notifications https://github.com/xtrinch/fcm-django APNS (Apple) APNs とは?設定と実装方法を解説! Generation of an APNS PEM file Establishing a token-based connection to APNs FCM/GCM(Google) Firebase でローコードなプッシュ通知を実装してみた Web Push https://github.com/safwanrahman/django-webpush https://github.com/web-push-libs/pywebpush https://github.com/jazzband/django-push-notifications https://github.com/elishowk/django-webpush-demo django-push-notifications を使って、Web Push 通知を実装する Web Push でブラウザにプッシュ通知を送ってみる ブラウザでプッシュ通知を実装してみた Web Push のサーバーサイドの処理〜VAPID と Message Encription を中心に〜 iOS16.4 から iOS の Safari でも Web プッシュが受け取れるようになったので AWS の機能でも試してみた! – 前編 (ローカルでの Web プッシュ通知と PWA 化) iOS16.4 から iOS の Safari でも Web プッシュが受け取れるようになったので AWS の機能でも試してみた! – 後編 (リモートでの Web プッシュ通知) Spec: ...

2024年2月27日 · 1 分

django-redis: lock

djanog-redis: cache.lock lock https://github.com/jazzband/django-redis/blob/d94a7f9644b96cc37743914fce899cca942c032a/django_redis/cache.py#L177 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class RedisCache(BaseCache): ... @omit_exception def lock(self, *args, **kwargs): return self.client.lock(*args, **kwargs) def __init__(self, server: str, params: Dict[str, Any]) -> None: ... self._client_cls = options.get( "CLIENT_CLASS", "django_redis.client.DefaultClient" ) self._client_cls = import_string(self._client_cls) self._client = None ... DefaultClient https://github.com/jazzband/django-redis/blob/d94a7f9644b96cc37743914fce899cca942c032a/django_redis/client/default.py#L29 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 from redis import Redis class DefaultClient: def __init__(self, server, params: Dict[str, Any], backend: BaseCache) -> None: ... self._clients: List[Optional[Redis]] = [None] * len(self._server) ... def lock( self, key: KeyT, version: Optional[int] = None, timeout: Optional[float] = None, sleep: float = 0.1, blocking_timeout: Optional[float] = None, client: Optional[Redis] = None, thread_local: bool = True, ): if client is None: client = self.get_client(write=True) key = self.make_key(key, version=version) return client.lock( key, timeout=timeout, sleep=sleep, blocking_timeout=blocking_timeout, thread_local=thread_local, ) def get_client( self, write: bool = True, tried: Optional[List[int]] = None, ) -> Redis: """ Method used for obtain a raw redis client. This function is used by almost all cache backend operations for obtain a native redis client/connection instance. """ index = self.get_next_client_index(write=write, tried=tried) if self._clients[index] is None: self._clients[index] = self.connect(index) return self._clients[index] # type:ignore Redis https://github.com/redis/redis-py/blob/6d77c6d715430c30f22147f8c572659d77380a9f/redis/client.py#L88 lock: https://github.com/redis/redis-py/blob/6d77c6d715430c30f22147f8c572659d77380a9f/redis/client.py#L439C1-L511C10 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 class Redis(RedisModuleCommands, CoreCommands, SentinelCommands): def lock( self, name: str, timeout: Optional[float] = None, sleep: float = 0.1, blocking: bool = True, blocking_timeout: Optional[float] = None, lock_class: Union[None, Any] = None, thread_local: bool = True, ): """ Return a new Lock object using key ``name`` that mimics the behavior of threading.Lock. If specified, ``timeout`` indicates a maximum life for the lock. By default, it will remain locked until release() is called. ``sleep`` indicates the amount of time to sleep per loop iteration when the lock is in blocking mode and another client is currently holding the lock. ``blocking`` indicates whether calling ``acquire`` should block until the lock has been acquired or to fail immediately, causing ``acquire`` to return False and the lock not being acquired. Defaults to True. Note this value can be overridden by passing a ``blocking`` argument to ``acquire``. ``blocking_timeout`` indicates the maximum amount of time in seconds to spend trying to acquire the lock. A value of ``None`` indicates continue trying forever. ``blocking_timeout`` can be specified as a float or integer, both representing the number of seconds to wait. ``lock_class`` forces the specified lock implementation. Note that as of redis-py 3.0, the only lock class we implement is ``Lock`` (which is a Lua-based lock). So, it's unlikely you'll need this parameter, unless you have created your own custom lock class. ``thread_local`` indicates whether the lock token is placed in thread-local storage. By default, the token is placed in thread local storage so that a thread only sees its token, not a token set by another thread. Consider the following timeline: time: 0, thread-1 acquires `my-lock`, with a timeout of 5 seconds. thread-1 sets the token to "abc" time: 1, thread-2 blocks trying to acquire `my-lock` using the Lock instance. time: 5, thread-1 has not yet completed. redis expires the lock key. time: 5, thread-2 acquired `my-lock` now that it's available. thread-2 sets the token to "xyz" time: 6, thread-1 finishes its work and calls release(). if the token is *not* stored in thread local storage, then thread-1 would see the token value as "xyz" and would be able to successfully release the thread-2's lock. In some use cases it's necessary to disable thread local storage. For example, if you have code where one thread acquires a lock and passes that lock instance to a worker thread to release later. If thread local storage isn't disabled in this case, the worker thread won't see the token set by the thread that acquired the lock. Our assumption is that these cases aren't common and as such default to using thread local storage.""" if lock_class is None: lock_class = Lock return lock_class( self, name, timeout=timeout, sleep=sleep, blocking=blocking, blocking_timeout=blocking_timeout, thread_local=thread_local, )

2024年1月5日 · 4 分

Python with文

With 【Python】with 文の構造を理解する

2023年11月9日 · 1 分