knuckleswtf / scribe

Generate API documentation for humans from your Laravel codebase.✍
https://scribe.knuckles.wtf/laravel/
MIT License
1.58k stars 280 forks source link

Request throttled when generating docs #765

Closed pratamatama closed 6 months ago

pratamatama commented 7 months ago

Scribe version

4.26.0

PHP version

8.2

Framework

Laravel

Framework version

10.33.0

Scribe config

title => "My App API Docs"
type => "laravel"
try_it_out.use_csrf => true
auth.enabled => true
auth.default => true
auth.name => "Authorization"

What happened?

When executing php artisan scribe:generate in a large project with hundreds of endpoints, I expect responses to return appropriate json data.

It gave Too Many Attempts instead.

Am I missing something?

Docs

shalvah commented 7 months ago

This sounds like you have a rare-limiting middleware in your application...?

pratamatama commented 7 months ago

@shalvah Yes, I'm using the default config for my API routes. Is there any way to disable it in the runtime? I saw some solution for testing but not for generating docs.

pratamatama commented 7 months ago

I mean, not all endpoints but some. Tried to clear view and config cache, still experiencing 429.

shalvah commented 7 months ago

You can try setting the config => app.env in the Scribe config file to docs, then disable the middleware in your RouteServiceProvider in the docs environment.

pratamatama commented 7 months ago

I was thinking about that in the first place. But I can't find a way to disable it.

I tried to set $this->withoutMiddleware(ThrottleRequests::class) inside the AppServiceProvider but it throws an error. Can you please point me to the right direction? I need your help.

EDIT The ThrottleRequests middleware must be enabled in production. And I don't know how (the syntax) to disable it in runtime. Like.. Adding conditional statement based on the environment.

shalvah commented 6 months ago

Where is the ThrottleRequests middleware registered? Show me the code.

pratamatama commented 6 months ago

its on the App\Http\Kernel.php

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array<int, class-string|string>
     */
    protected $middleware = [
        // \App\Http\Middleware\TrustHosts::class,
        \App\Http\Middleware\TrustProxies::class,
        \Illuminate\Http\Middleware\HandleCors::class,
        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    ];

    /**
     * The application's route middleware groups.
     *
     * @var array<string, array<int, class-string|string>>
     */
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            \Illuminate\Routing\Middleware\ThrottleRequests::class . ':api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
            \App\Http\Middleware\LocaleMiddleware::class,
        ],
    ];

    /**
     * The application's middleware aliases.
     *
     * Aliases may be used instead of class names to conveniently assign middleware to routes and groups.
     *
     * @var array<string, class-string|string>
     */
    protected $middlewareAliases = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
        'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
        'signed' => \App\Http\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
        'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
        'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class,
        'locale' => \App\Http\Middleware\LocaleMiddleware::class,
    ];
}
shalvah commented 6 months ago

Check your RouteServiceProvider. This is where the middleware is applied.

shalvah commented 6 months ago

As for doing it by environment, I'm not sure. You could try something like this. Or replace the Laravel middleware with a custom class that inherits from it (similar to this auth middleware); and checks the environment before passing the request on.

pratamatama commented 6 months ago

Thanks @shalvah , will try that and close this issue if it works. Thanks for your time!

pratamatama commented 6 months ago

I managed to get it works by defining a conditional statement inside the RouteServiceProvider.

RateLimiter::for('api', function (Request $request) {
    $perMinute = config('app.env') === 'documentation' ? 999999 : 60;

    return Limit::perMinute($perMinute)->by($request->user()?->id ?: $request->ip());
});

Wrapping the whole invocation making it throttles every requests immediately. I think because when it's not specified, Laravel will assume that I want no one to access the api.

Thanks again for your help @shalvah , really appreciate.