From 394babfff801dccb361528f254fff8fd6260ad32 Mon Sep 17 00:00:00 2001 From: yuuki <> Date: Fri, 24 Jan 2025 11:04:45 +0900 Subject: [PATCH] =?UTF-8?q?=E5=89=8A=E9=99=A4=E3=81=AB=E3=83=AC=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=83=AA=E3=83=9F=E3=83=83=E3=83=88=E3=82=92=E6=8E=9B?= =?UTF-8?q?=E3=81=91=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++++++ app.py | 31 +++++++++++++++++++++++-------- requirements.txt | 1 + 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 1f7dceb..af20f42 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,14 @@ docker run --detach \ mongo ``` +```bash +docker run --detach \ + --name litey-redis-debug \ + --volume litey-redis-debug:/data/db \ + --publish 6379:6379 \ + redis +``` + サーバーを起動します ```bash diff --git a/app.py b/app.py index f1362de..0f1018a 100644 --- a/app.py +++ b/app.py @@ -7,15 +7,17 @@ from urllib.parse import urlparse from base64 import b64encode from re import escape, compile, IGNORECASE from pprint import pprint -from random import randint from requests import get -from fastapi import FastAPI, Request, Response, status +from fastapi import FastAPI, Request, Response, status, Depends from fastapi.responses import JSONResponse, Response, PlainTextResponse, FileResponse from fastapi.templating import Jinja2Templates from pydantic import BaseModel from pymongo import MongoClient, DESCENDING from contextlib import asynccontextmanager +from redis.asyncio import from_url +from fastapi_limiter import FastAPILimiter +from fastapi_limiter.depends import RateLimiter # Jinja2 @@ -50,6 +52,17 @@ def is_over_n_hours(src: datetime, hours: int) -> bool: ctx = {} +async def default_identifier(req: Request): + cloudflare_ip = req.headers.get("CF-Connecting-IP") + if cloudflare_ip: + return cloudflare_ip.split(",")[0] + + forwarded = req.headers.get("X-Forwarded-For") + if forwarded: + return forwarded.split(",")[0] + + return req.client.host + ":" + req.scope["path"] + @asynccontextmanager async def lifespan(app: FastAPI): ctx["templates"] = Jinja2Templates("templates") @@ -71,11 +84,17 @@ async def lifespan(app: FastAPI): ctx["mongo_client"].litey.ngs.create_index("word", unique=True) pprint(ctx) + + redis_uri = environ.get("REDIS_URI", "redis://127.0.0.1:6379/") + redis_connection = from_url(redis_uri, encoding="utf8") + await FastAPILimiter.init(redis_connection, identifier=default_identifier) yield ctx["mongo_client"].close() ctx.clear() + await FastAPILimiter.close() + # スニペット class LiteYItem(BaseModel): @@ -112,7 +131,7 @@ def fastapi_serve(dir: str, ref: str, indexes: List[str] = ["index.html", "index return FileResponse(path) def get_ip(req: Request) -> str: - return req.headers.get("CF-Connecting-IP") or req.client.host + return req.headers.get("CF-Connecting-IP") or req.headers.get("X-Forwarded-For") or req.client.host def get_litey_notes(id: str = None) -> List[dict]: if not id: @@ -162,12 +181,8 @@ async def api_post(item: LiteYItem, req: Request): return PlainTextResponse("OK") -@app.post("/api/litey/delete") +@app.post("/api/litey/delete", dependencies=[Depends(RateLimiter(times=1, seconds=86400))]) async def api_delete(item: LiteYDeleteItem): - rand = randint(1, 100) - if rand != 100: - return PlainTextResponse(f"{rand} は 100 ではありません。", 403) - ctx["mongo_client"].litey.notes.delete_one({ "id": item.id }) diff --git a/requirements.txt b/requirements.txt index f3ab4ad..c101202 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ requests==2.32.3 fastapi[standard]==0.115.7 pymongo==4.10.1 Jinja2==3.1.5 +fastapi-limiter==0.1.6