{"id":24870935,"url":"https://github.com/mathcale/goexpert-rate-limiter-challenge","last_synced_at":"2026-04-28T22:34:22.015Z","repository":{"id":220987893,"uuid":"749527509","full_name":"mathcale/goexpert-rate-limiter-challenge","owner":"mathcale","description":"A rate limiter HTTP middleware implementation for the Go Expert specialization course","archived":false,"fork":false,"pushed_at":"2024-02-16T01:47:37.000Z","size":358,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-11T19:09:58.411Z","etag":null,"topics":["golang","proof-of-work","rate-limiter","redis"],"latest_commit_sha":null,"homepage":"","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mathcale.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-28T20:36:30.000Z","updated_at":"2024-02-11T23:30:10.000Z","dependencies_parsed_at":"2024-06-19T20:17:08.232Z","dependency_job_id":"3c9afd3d-00d2-4c19-aecd-73ffac5fb6ec","html_url":"https://github.com/mathcale/goexpert-rate-limiter-challenge","commit_stats":null,"previous_names":["mathcale/goexpert-rate-limiter-challenge"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mathcale/goexpert-rate-limiter-challenge","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mathcale%2Fgoexpert-rate-limiter-challenge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mathcale%2Fgoexpert-rate-limiter-challenge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mathcale%2Fgoexpert-rate-limiter-challenge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mathcale%2Fgoexpert-rate-limiter-challenge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mathcale","download_url":"https://codeload.github.com/mathcale/goexpert-rate-limiter-challenge/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mathcale%2Fgoexpert-rate-limiter-challenge/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32402670,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-28T19:38:08.556Z","status":"ssl_error","status_checked_at":"2026-04-28T19:37:55.688Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","proof-of-work","rate-limiter","redis"],"created_at":"2025-02-01T04:17:43.012Z","updated_at":"2026-04-28T22:34:22.000Z","avatar_url":"https://github.com/mathcale.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Go Expert Challenge - Rate Limiter\n\nImplementação de um rate limiter em Go para um serviço Web capaz de limitar o número de requisições recebidas de clientes dentro de um intervalo de tempo configurável, observando o endereço IP e/ou token de acesso `API_KEY`.\n\n## Arquitetura\n\n![High Level Architecture](./docs/diagram/high-level-diagram.png)\n\nA aplicação é composta por um servidor web que recebe requisições HTTP e um middleware de rate limiter que é responsável por controlar o número de requisições recebidas. O [middleware](internal/infra/web/middlewares/ratelimiter.go) intercepta todas as requisições e executa a lógica de rate limiting a partir da instância de [`RateLimiter`](internal/pkg/ratelimiter/limiter.go), o qual contém as regras de negócio do limitador e sabe invocar a _Strategy_ de armazenamento instanciada pelo [gerenciador de dependências](internal/pkg/dependencyinjector/injector.go) para realizar a checagem de limites.\n\nO rate limiter é configurável para realizar a checagem de limites por IP ou token `API_KEY`, e utiliza o Redis como _storage_ para armazenar a quantidade de requisições realizadas por cada IP e/ou token. Tal configuração é realizada através de variáveis de ambiente declaradas no arquivo `.env` e injetadas na aplicação através do [gerenciador de dependências](internal/pkg/dependencyinjector/injector.go), no boot da aplicação. As variáveis de ambiente são:\n\n- `RATE_LIMITER_IP_MAX_REQUESTS`: Número máximo de requisições por IP\n- `RATE_LIMITER_TOKEN_MAX_REQUESTS`: Número máximo de requisições por token\n- `RATE_LIMITER_TIME_WINDOW_MILISECONDS`: Janela de tempo de vida em milissegundos\n\nExemplo de configuração para limitar 10 requisições por IP e 100 requisições por token em uma janela de tempo de 5 minutos:\n\n```sh\nRATE_LIMITER_IP_MAX_REQUESTS=10\nRATE_LIMITER_TOKEN_MAX_REQUESTS=100\nRATE_LIMITER_TIME_WINDOW_MILISECONDS=300000\n```\n\nNeste caso, o rate limiter irá bloquear requisições que excedam o limite configurado, retornando um status `429 Too Many Requests` e um corpo em JSON `{\"message\":\"rate limit exceeded\"}`, além de informar os cabeçalhos `X-Ratelimit-Limit`, `X-Ratelimit-Remaining` e `X-Ratelimit-Reset` com informações sobre o limite, quantidade restante e tempo de reset, respectivamente. Novas requisições que excedam o limite configurado serão bloqueadas até que o tempo de reset (5 minutos, no caso) seja atingido.\n\n### Estratégia de armazenamento\n\nA estratégia de armazenamento é definida através de uma interface `LimiterStrategyInterface` que possui o método `Check` para obter e definir valores no _storage_. No momento, a aplicação possui apenas uma implementação para o Redis, mas é possível adicionar novas implementações para outros _storages_ como memória, banco de dados, etc, sem alterar a lógica de rate limiting, apenas injetando a nova implementação na instância de `RateLimiter` através do [gerenciador de dependências](internal/pkg/dependencyinjector/injector.go).\n\n![Strategy diagram](./docs/diagram/strategy-diagram.png)\n\n## Benchmarks\n\nFoi utilizado o [Grafana k6](https://k6.io/) para realizar testes de carga do tipo [_smoke_](https://grafana.com/docs/k6/latest/testing-guides/test-types/smoke-testing/) e [_stress_](https://grafana.com/docs/k6/latest/testing-guides/test-types/stress-testing/) no serviço para avaliar o comportamento da solução desenvolvida. Os resultados se encontram [aqui](./BENCHMARKS.md).\n\n## Executando o projeto\n\n**Obs:** é necessário ter o [Docker](https://www.docker.com/) e [Docker Compose](https://docs.docker.com/compose/) instalados.\n\n1. Crie um arquivo `.env` na raiz do projeto copiando o conteúdo de `.env.example` e ajuste-o conforme necessário. Por padrão, os seguintes valores são utilizados:\n\n```sh\nLOG_LEVEL=\"debug\" # Nível de log da aplicação\nWEB_SERVER_PORT=8080 # Porta do servidor Web\n\n# Configurações do Redis\nREDIS_HOST=\"localhost\"\nREDIS_PORT=6379\nREDIS_PASSWORD=\"\"\nREDIS_DB=0\n\nRATE_LIMITER_IP_MAX_REQUESTS=10 # Número máximo de requisições por IP\nRATE_LIMITER_TOKEN_MAX_REQUESTS=100 # Número máximo de requisições por token\nRATE_LIMITER_TIME_WINDOW_MILISECONDS=1000 # Janela de tempo em milissegundos\n```\n\n2. Execute o comando `docker compose up redis api` para iniciar a aplicação e o Redis.\n\n### Exemplos de requisições\n\n- **Requisição com checagem via IP com sucesso:**\n\n```sh\n$ curl -vvv http://localhost:8080\n\n* processing: http://localhost:8080\n*   Trying [::1]:8080...\n* Connected to localhost (::1) port 8080\n\u003e GET / HTTP/1.1\n\u003e Host: localhost:8080\n\u003e User-Agent: curl/8.2.1\n\u003e Accept: */*\n\u003e \n\u003c HTTP/1.1 200 OK\n\u003c Accept: application/json\n\u003c Content-Type: application/json\n\u003c X-Ratelimit-Limit: 10\n\u003c X-Ratelimit-Remaining: 9\n\u003c X-Ratelimit-Reset: 1707691706\n\u003c Date: Sun, 11 Feb 2024 22:48:25 GMT\n\u003c Content-Length: 27\n\u003c \n{\"message\":\"Hello World!\"}\n```\n\n- **Requisição com checagem via token com sucesso:**\n\n```sh\n$ curl -H 'API_KEY: some-api-key-123' -vvv http://localhost:8080\n\n* processing: http://localhost:8080\n*   Trying [::1]:8080...\n* Connected to localhost (::1) port 8080\n\u003e GET / HTTP/1.1\n\u003e Host: localhost:8080\n\u003e User-Agent: curl/8.2.1\n\u003e Accept: */*\n\u003e API_KEY: some-api-key-123\n\u003e \n\u003c HTTP/1.1 200 OK\n\u003c Accept: application/json\n\u003c Content-Type: application/json\n\u003c X-Ratelimit-Limit: 100\n\u003c X-Ratelimit-Remaining: 99\n\u003c X-Ratelimit-Reset: 1707692138\n\u003c Date: Sun, 11 Feb 2024 22:55:37 GMT\n\u003c Content-Length: 27\n\u003c \n{\"message\":\"Hello World!\"}\n```\n\n- **Requisição com checagem via IP bloqueada:**\n\n```sh\n$ curl -vvv http://localhost:8080\n\n* processing: http://localhost:8080\n*   Trying [::1]:8080...\n* Connected to localhost (::1) port 8080\n\u003e GET / HTTP/1.1\n\u003e Host: localhost:8080\n\u003e User-Agent: curl/8.2.1\n\u003e Accept: */*\n\u003e \n\u003c HTTP/1.1 200 OK\n\u003c Accept: application/json\n\u003c Content-Type: application/json\n\u003c X-Ratelimit-Limit: 10\n\u003c X-Ratelimit-Remaining: 0\n\u003c X-Ratelimit-Reset: 1707691750\n\u003c Date: Sun Feb 11 2024 22:49:09 GMT\n\u003c Content-Length: 33\n\u003c \n{\"message\":\"rate limit exceeded\"}\n```\n\n- **Requisição com checagem via token bloqueada:**\n\n```sh\n$ curl -H 'API_KEY: some-api-key-123' -vvv http://localhost:8080\n\n* processing: http://localhost:8080\n*   Trying [::1]:8080...\n* Connected to localhost (::1) port 8080\n\u003e GET / HTTP/1.1\n\u003e Host: localhost:8080\n\u003e User-Agent: curl/8.2.1\n\u003e Accept: */*\n\u003e API_KEY: some-api-key-123\n\u003e \n\u003c HTTP/1.1 200 OK\n\u003c Accept: application/json\n\u003c Content-Type: application/json\n\u003c X-Ratelimit-Limit: 100\n\u003c X-Ratelimit-Remaining: 99\n\u003c X-Ratelimit-Reset: 1707692150\n\u003c Date: Sun Feb 11 2024 22:55:49 GMT\n\u003c Content-Length: 33\n\u003c \n{\"message\":\"rate limit exceeded\"}\n```\n\n## Testes\n\n### Testes de unidade\n\nPara executar os testes de unidade e validar a cobertura, execute o comando `make test`.\n\n### Testes de estresse\n\nPara executar os testes de estresse com k6, siga os passos:\n\n1. Inicie a aplicação e o Redis com o comando `docker compose up redis api`;\n2. Execute o comando `make test_k6_smoke` para iniciar o teste de estresse do tipo _smoke_ (duração de 1 minuto);\n3. Execute o comando `make test_k6_stress` para iniciar o teste de estresse do tipo _stress_ (duração de 40 minutos).\n\n**Obs:** talvez seja necessário fechar alguns programas em seu computador para que o teste de estresse não seja afetado, pois ele consome muitos recursos.\n\nÉ possível visualizar os resultados dos testes obtidos por mim nas pastas `./scripts/k6/smoke` e `./scripts/k6/stress`, tanto em formato de texto quanto em HTML.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmathcale%2Fgoexpert-rate-limiter-challenge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmathcale%2Fgoexpert-rate-limiter-challenge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmathcale%2Fgoexpert-rate-limiter-challenge/lists"}