آرش قصد دارد یک وبسرویس خفن پیادهسازی کند، اما او میداند که از همان ابتدای کار، تعداد درخواستهای کاربران به *endpoint* هایی که بار پردازشی بالایی دارند بسیار زیاد خواهد بود. بنابراین او تصمیم گرفته که از *rate limiter* استفاده کند. از آنجایی که او نمیداند این *rate limiter* را چگونه پیادهسازی کند، از شما خواسته تا آن را برایش پیادهسازی کنید.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/problemset/assignments/4367/download_problem_initial_project/126108/) دانلود کنید. ساختار فایلهای پروژه بهصورت زیر است:
```
rate-limit
├── db
│ └── connection.go
├── handlers
│ └── root.go
├── limiters
│ ├── by_app_key.go
│ └── by_ip.go
├── test
│ └── ratelimit_sample_test.go
├── go.mod
├── go.sum
└── main.go
```
%align_right_start%
وابستگی [`time/rate`](https://pkg.go.dev/golang.org/x/time/rate) در پروژه تعریف شده و برای پیادهسازی *rate limiter* باید از آن استفاده کنید.
%align_end%
## پایگاه داده
در این پروژه از پایگاه دادهی *PostgreSQL* استفاده شده است. برنامه شامل جدولی با نام `app_keys` است که شامل دو ستون `id` از نوع `BIGSERIAL` و `key` از نوع `VARCHAR(255)` است.
در پکیج `db` تابعی با نام `GetConnection` تعریف شده که باید از آن برای دریافت کانکشن دیتابیس استفاده کنید. میتوانید اطلاعات اتصال به دیتابیس را در این فایل تغییر دهید.
## روتها
تنها یک روت با آدرس `/` در برنامه تعریف شده است که میتوان *rate limiter* های مختلف را روی *handler* آن اعمال کرد.
## تابع `ByIp`
این تابع بهترتیب شامل سه پارامتر زیر است:
+ `next` از نوع `http.Handler`: همان *handler* اصلی روت است.
+ `refillRate` از نوع `rate.Limit`: میزان افزایش تعداد درخواستهای مجاز کاربر در هر ثانیه (برای مثال اگر مقدار آن برابر با ۳ باشد، در هر ثانیه، ۳ واحد به تعداد درخواستهای مجاز کاربر اضافه میشود، به شرطی که تعداد درخواستهای مجاز فعلیاش کوچکتر از `tokenBucketSize` باشد.)
+ `tokenBucketSize` از نوع `int`: سقف تعداد درخواستهای پیاپی کاربر
این تابع را طوری پیادهسازی کنید که درخواستها را براساس آیپی محدود کند. در صورتی که کاربر با محدودیت مواجه نشده باشد، پردازش درخواست باید توسط `next` صورت گیرد. در غیر اینصورت، کد پاسخ باید به `429` تغییر کند، مقدار هدر `Content-Type` باید برابر با `application/json` قرار داده شود و بدنهی پاسخ بهصورت زیر باشد:
```json
{"error": "too many requests"}
```
## تابع `ByAppKey`
امضای این تابع مشابه تابع `ByIp` است.
این تابع را طوری پیادهسازی کنید که درخواستها را براساس مقدار هدر `X-App-Key` موجود در درخواست محدود کند. اگر مقدار این هدر در جدول `app_keys` وجود نداشته باشد، محدودیت نباید اعمال شود. در غیر اینصورت، محدودیت باید اعمال شود. اگر کاربر با محدودیت مواجه شده باشد، پاسخ باید مشابه پاسخ مورد انتظار برای تابع `ByIp` باشد.
**تضمین میشود** که هنگام استفاده از این تابع، هدر `X-App-Key` در درخواست موجود است.
## ترکیب *rate limiter* ها
ممکن است بخواهیم *rate limiter* ها را ترکیب کنیم. اگر از توابع `ByIp` و `ByAppKey` در کنار یکدیگر استفاده شود، محدودیت باید هم براساس آیپی و هم براساس مقدار هدر `X-App-Key` اعمال شود. مثلاً اگر مقدار `tokenBucketSize` برابر با ۵ باشد و ۵ درخواست پیاپی با آیپیهای مختلف، اما با `X-App-Key` یکسان (بهطوری که در جدول `app_keys` موجود باشد) ارسال کنیم، باید با محدودیت مواجه شویم. همچنین اگر این درخواستها را با آیپی یکسان، اما با `X-App-Key`های متفاوت (بهطوری که در جدول `app_keys` موجود باشند) ارسال کنیم، باز هم باید با محدودیت مواجه شویم.
# نکات
+ نیازی به *persistent* بودن اطلاعات مربوط به *rate limiter* ها نیست (با خاتمهی برنامه، دادههای مربوط به *rate limiter* ها میتوانند از بین بروند).
+ شما تنها مجاز به اعمال تغییرات در پوشهی `limiters` هستید.
+ در صورت نیاز، میتوانید فایلهای جدیدی در پوشهی `limiters` تعریف کنید.
# آنچه باید آپلود کنید
پس از پیادهسازی موارد خواستهشده، پوشهی `limiters` را زیپ کرده و آپلود کنید.