Closed labomatik closed 5 months ago
Hey @labomatik I'm sorry for the delay in getting back to you and thank you very much for the GitHub Sponsor! It means a lot. 405 usually means that the HTTP status code is wrong - the getAccessToken
method usually uses a POST
request. Does the API you are integrating with require a different HTTP status code? Maybe PUT/PATCH?
Hello, No problem for the delay :-) It's an open source package
I've tried this already in postman to make sure the API is not using another HTTP request
The result is a correct json {"error":"invalid_client"} .
This is also available in the API doc: https://documentation.acc.connect.easypost.eu/static/index.html
Somehow it's not working with the request i'm doing with saloon. I guess it's something stupid i didn't configured...
I've tried the new ->debug() with no luck... The result is the same, seems like saloon didn't execute the request.
@labomatik Hi!
Have you tried wrapping the getAccessToken()
in try-catch for the RequestException, and then dump the Request, and PendingRequest?
It'd be interesting to see which Methods they show, since HTTP 405 means that the HTTP method used is not allowed.
Also do dump the headers on the Response, because they must return an Allow header with the HTTP methods allowed.
Would it also be possible to get some more of the Connector code, as well as a full stack trace? I can't see anything immediately obvious in the Saloon code that'd cause this. So any code and details you can provide would help.
This is the trace:
App\Http\Integrations\EasyPost\EasyPostConnector#1
(
[*:authenticator] => null
[*:headers] => Saloon\Repositories\ArrayStore#2
(
[*:data] => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
]
)
[*:query] => Saloon\Repositories\ArrayStore#3
(
[*:data] => [],
)
[*:config] => Saloon\Repositories\ArrayStore#4
(
[*:data] => [],
)
[*:middlewarePipeline] => Saloon\Helpers\MiddlewarePipeline#5
(
[*:requestPipeline] => Saloon\Helpers\Pipeline#6
(
[*:pipes] => [],
)
[*:responsePipeline] => Saloon\Helpers\Pipeline#7
(
[*:pipes] => [],
)
)
[*:delay] => Saloon\Repositories\IntegerStore#8
(
[*:data] => null
)
[*:response] => null
[*:mockClient] => null
[*:defaultSender] => ''
[*:sender] => Saloon\Http\Senders\GuzzleSender#9
(
[*:client] => GuzzleHttp\Client#10
(
[GuzzleHttp\Client:config] => [
'crypto_method' => 33,
'connect_timeout' => 10,
'timeout' => 30,
'http_errors' => true,
'handler' => GuzzleHttp\HandlerStack#11
(
[GuzzleHttp\HandlerStack:handler] => Closure(...)
[GuzzleHttp\HandlerStack:stack] => [...],
[GuzzleHttp\HandlerStack:cached] => Closure(...)
),
'allow_redirects' => [
'max' => 5,
'protocols' => [...],,
'strict' => false,
'referer' => false,
'track_redirects' => false,
],
'decode_content' => true,
'verify' => true,
'cookies' => false,
'idn_conversion' => false,
'headers' => [
'User-Agent' => 'GuzzleHttp/7',
],
]
)
[*:handlerStack] => GuzzleHttp\HandlerStack#11(...)
)
[tries] => null
[retryInterval] => null
[useExponentialBackoff] => null
[throwOnMaxTries] => null
[*:connectTimeout] => 60
[*:requestTimeout] => 120
[*:oauthConfig] => Saloon\Helpers\OAuth2\OAuthConfig#12
(
[*:clientId] => 'qsd'
[*:clientSecret] => 'sd'
[*:redirectUri] => ''
[*:authorizeEndpoint] => 'authorize'
[*:tokenEndpoint] => 'https://auth.acc.connect.easypost.eu/oauth2/token'
[*:userEndpoint] => 'user'
[*:requestModifier] => null
[*:defaultScopes] => [
0 => 'connect/read:jobs',
1 => 'connect/submit:jobs',
2 => 'connect/read:sending-events',
]
)
)
Saloon\Exceptions\Request\Statuses\MethodNotAllowedException#1
(
[*:message] => 'Method Not Allowed (405) Response: '
[Exception:string] => ''
[*:code] => 0
[*:file] => 'vendor/saloonphp/saloon/src/Helpers/RequestExceptionHelper.php'
[*:line] => 51
[Exception:trace] => [
0 => [
'file' => 'vendor/saloonphp/saloon/src/Http/Response.php',
'line' => 450,
'function' => 'create',
'class' => 'Saloon\\Helpers\\RequestExceptionHelper',
'type' => '::',
'args' => [
0 => Saloon\Http\Response#2
(
[*:psrRequest] => GuzzleHttp\Psr7\Request(...)
[*:psrResponse] => GuzzleHttp\Psr7\Response(...)
[*:pendingRequest] => Saloon\Http\PendingRequest(...)
[*:senderException] => GuzzleHttp\Exception\ClientException(...)
[*:mocked] => false
[*:cached] => false
[*:fakeResponse] => null
),
1 => GuzzleHttp\Exception\ClientException#3
(
[*:message] => 'Client error: `POST https://auth.acc.connect.easypost.eu/oauth2/token` resulted in a `405 Method Not Allowed` response'
[Exception:string] => ''
[*:code] => 405
[*:file] => 'vendor/guzzlehttp/guzzle/src/Exception/RequestException.php'
[*:line] => 113
[Exception:trace] => [...],
[Exception:previous] => null
[GuzzleHttp\Exception\RequestException:request] => GuzzleHttp\Psr7\Request(...)
[GuzzleHttp\Exception\RequestException:response] => GuzzleHttp\Psr7\Response(...)
[GuzzleHttp\Exception\RequestException:handlerContext] => [...],
),
],
],
1 => [
'file' => 'vendor/saloonphp/saloon/src/Http/Response.php',
'line' => 428,
'function' => 'createException',
'class' => 'Saloon\\Http\\Response',
'type' => '->',
'args' => [],,
],
2 => [
'file' => 'vendor/saloonphp/saloon/src/Http/Response.php',
'line' => 462,
'function' => 'toException',
'class' => 'Saloon\\Http\\Response',
'type' => '->',
'args' => [],,
],
3 => [
'file' => 'vendor/saloonphp/saloon/src/Traits/Plugins/AlwaysThrowOnErrors.php',
'line' => 23,
'function' => 'throw',
'class' => 'Saloon\\Http\\Response',
'type' => '->',
'args' => [],,
],
4 => [
'file' => 'vendor/saloonphp/saloon/src/Helpers/MiddlewarePipeline.php',
'line' => 87,
'function' => 'Saloon\\Traits\\Plugins\\{closure}',
'class' => 'App\\Http\\Integrations\\EasyPost\\EasyPostConnector',
'type' => '::',
'args' => [
0 => Saloon\Http\Response#2(...),
],
],
5 => [
'function' => 'Saloon\\Helpers\\{closure}',
'class' => 'Saloon\\Helpers\\MiddlewarePipeline',
'type' => '::',
'args' => [
0 => Saloon\Http\Response#2(...),
],
],
6 => [
'file' => 'vendor/saloonphp/saloon/src/Helpers/Pipeline.php',
'line' => 45,
'function' => 'call_user_func',
'args' => [
0 => Closure#4
(
[0] => Closure#4(...)
),
1 => Saloon\Http\Response#2(...),
],
],
7 => [
'file' => 'vendor/saloonphp/saloon/src/Helpers/MiddlewarePipeline.php',
'line' => 108,
'function' => 'process',
'class' => 'Saloon\\Helpers\\Pipeline',
'type' => '->',
'args' => [
0 => Saloon\Http\Response#2(...),
],
],
8 => [
'file' => 'vendor/saloonphp/saloon/src/Http/PendingRequest.php',
'line' => 152,
'function' => 'executeResponsePipeline',
'class' => 'Saloon\\Helpers\\MiddlewarePipeline',
'type' => '->',
'args' => [
0 => Saloon\Http\Response#2(...),
],
],
9 => [
'file' => 'vendor/saloonphp/saloon/src/Traits/Connector/SendsRequests.php',
'line' => 78,
'function' => 'executeResponsePipeline',
'class' => 'Saloon\\Http\\PendingRequest',
'type' => '->',
'args' => [
0 => Saloon\Http\Response#2(...),
],
],
10 => [
'file' => 'vendor/saloonphp/saloon/src/Traits/OAuth2/ClientCredentialsGrant.php',
'line' => 40,
'function' => 'send',
'class' => 'Saloon\\Http\\Connector',
'type' => '->',
'args' => [
0 => Saloon\Http\OAuth2\GetClientCredentialsTokenRequest#5
(
[*:method] => Saloon\Enums\Method(...)
[*:authenticator] => null
[*:headers] => Saloon\Repositories\ArrayStore(...)
[*:query] => Saloon\Repositories\ArrayStore(...)
[*:config] => Saloon\Repositories\ArrayStore(...)
[*:middlewarePipeline] => Saloon\Helpers\MiddlewarePipeline(...)
[*:delay] => Saloon\Repositories\IntegerStore(...)
[*:response] => null
[*:mockClient] => null
[tries] => null
[retryInterval] => null
[useExponentialBackoff] => null
[throwOnMaxTries] => null
[*:oauthConfig] => Saloon\Helpers\OAuth2\OAuthConfig(...)
[*:scopes] => [...],
[*:scopeSeparator] => ' '
[*:body] => Saloon\Repositories\Body\FormBodyRepository(...)
),
],
],
11 => [
'file' => 'app/Console/Commands/TEMPEasyPostApi.php',
'line' => 37,
'function' => 'getAccessToken',
'class' => 'App\\Http\\Integrations\\EasyPost\\EasyPostConnector',
'type' => '->',
'args' => [
0 => [
0 => 'connect/read:jobs',
1 => 'connect/submit:jobs',
2 => 'connect/read:sending-events',
],
],
],
12 => [
'file' => 'vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php',
'line' => 36,
'function' => 'handle',
'class' => 'App\\Console\\Commands\\TEMPEasyPostApi',
'type' => '->',
'args' => [],,
],
13 => [
'file' => 'vendor/laravel/framework/src/Illuminate/Container/Util.php',
'line' => 41,
'function' => 'Illuminate\\Container\\{closure}',
'class' => 'Illuminate\\Container\\BoundMethod',
'type' => '::',
'args' => [],,
],
14 => [
'file' => 'vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php',
'line' => 93,
'function' => 'unwrapIfClosure',
'class' => 'Illuminate\\Container\\Util',
'type' => '::',
'args' => [
0 => Closure#6
(
[0] => Closure#6(...)
),
],
],
15 => [
'file' => 'vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php',
'line' => 37,
'function' => 'callBoundMethod',
'class' => 'Illuminate\\Container\\BoundMethod',
'type' => '::',
'args' => [
0 => Illuminate\Foundation\Application#7
(
[*:resolved] => [...],
[*:bindings] => [...],
[*:methodBindings] => [...],
[*:instances] => [...],
[*:scopedInstances] => [...],
[*:aliases] => [...],
[*:abstractAliases] => [...],
[*:extenders] => [...],
[*:tags] => [...],
[*:buildStack] => [...],
[*:with] => [...],
[contextual] => [...],
[*:reboundCallbacks] => [...],
[*:globalBeforeResolvingCallbacks] => [...],
[*:globalResolvingCallbacks] => [...],
[*:globalAfterResolvingCallbacks] => [...],
[*:beforeResolvingCallbacks] => [...],
[*:resolvingCallbacks] => [...],
[*:afterResolvingCallbacks] => [...],
[*:basePath] => ''
[*:hasBeenBootstrapped] => true
[*:booted] => true
[*:bootingCallbacks] => [...],
[*:bootedCallbacks] => [...],
[*:terminatingCallbacks] => [...],
[*:serviceProviders] => [...],
[*:loadedProviders] => [...],
[*:deferredServices] => [...],
[*:bootstrapPath] => 'bootstrap'
[*:appPath] => null
[*:configPath] => null
[*:databasePath] => null
[*:langPath] => 'lang'
[*:publicPath] => null
[*:storagePath] => null
[*:environmentPath] => null
[*:environmentFile] => '.env'
[*:isRunningInConsole] => true
[*:namespace] => 'App\\'
[*:absoluteCachePathPrefixes] => [...],
),
1 => [
0 => App\Console\Commands\TEMPEasyPostApi(...),
1 => 'handle',
],
2 => Closure#6(...),
],
],
16 => [
'file' => 'vendor/laravel/framework/src/Illuminate/Container/Container.php',
'line' => 662,
'function' => 'call',
'class' => 'Illuminate\\Container\\BoundMethod',
'type' => '::',
'args' => [
0 => Illuminate\Foundation\Application#7(...),
1 => [
0 => App\Console\Commands\TEMPEasyPostApi(...),
1 => 'handle',
],
2 => [],,
3 => null,
],
],
17 => [
'file' => 'vendor/laravel/framework/src/Illuminate/Console/Command.php',
'line' => 211,
'function' => 'call',
'class' => 'Illuminate\\Container\\Container',
'type' => '->',
'args' => [
0 => [
0 => App\Console\Commands\TEMPEasyPostApi(...),
1 => 'handle',
],
],
],
18 => [
'file' => 'vendor/symfony/console/Command/Command.php',
'line' => 326,
'function' => 'execute',
'class' => 'Illuminate\\Console\\Command',
'type' => '->',
'args' => [
0 => Symfony\Component\Console\Input\ArgvInput#8
(
[*:definition] => Symfony\Component\Console\Input\InputDefinition(...)
[*:stream] => null
[*:options] => [...],
[*:arguments] => [...],
[*:interactive] => true
[Symfony\Component\Console\Input\ArgvInput:tokens] => [...],
[Symfony\Component\Console\Input\ArgvInput:parsed] => [...],
),
1 => Illuminate\Console\OutputStyle#9
(
[Symfony\Component\Console\Style\OutputStyle:output] => Symfony\Component\Console\Output\ConsoleOutput(...)
[Symfony\Component\Console\Style\SymfonyStyle:input] => Symfony\Component\Console\Input\ArgvInput#8(...)
[Symfony\Component\Console\Style\SymfonyStyle:output] => Symfony\Component\Console\Output\ConsoleOutput(...)
[Symfony\Component\Console\Style\SymfonyStyle:lineLength] => 120
[Symfony\Component\Console\Style\SymfonyStyle:bufferedOutput] => Symfony\Component\Console\Output\TrimmedBufferOutput(...)
[Illuminate\Console\OutputStyle:output] => Symfony\Component\Console\Output\ConsoleOutput(...)
[*:newLinesWritten] => 1
[*:newLineWritten] => false
),
],
],
19 => [
'file' => 'vendor/laravel/framework/src/Illuminate/Console/Command.php',
'line' => 181,
'function' => 'run',
'class' => 'Symfony\\Component\\Console\\Command\\Command',
'type' => '->',
'args' => [
0 => Symfony\Component\Console\Input\ArgvInput#8(...),
1 => Illuminate\Console\OutputStyle#9(...),
],
],
20 => [
'file' => 'vendor/symfony/console/Application.php',
'line' => 1096,
'function' => 'run',
'class' => 'Illuminate\\Console\\Command',
'type' => '->',
'args' => [
0 => Symfony\Component\Console\Input\ArgvInput#8(...),
1 => Symfony\Component\Console\Output\ConsoleOutput#10
(
[Symfony\Component\Console\Output\Output:verbosity] => 32
[Symfony\Component\Console\Output\Output:formatter] => Symfony\Component\Console\Formatter\OutputFormatter(...)
[Symfony\Component\Console\Output\StreamOutput:stream] => {resource}
[Symfony\Component\Console\Output\ConsoleOutput:stderr] => Symfony\Component\Console\Output\StreamOutput(...)
[Symfony\Component\Console\Output\ConsoleOutput:consoleSectionOutputs] => [...],
),
],
],
21 => [
'file' => 'vendor/symfony/console/Application.php',
'line' => 324,
'function' => 'doRunCommand',
'class' => 'Symfony\\Component\\Console\\Application',
'type' => '->',
'args' => [
0 => App\Console\Commands\TEMPEasyPostApi#11
(
[Symfony\Component\Console\Command\Command:application] => Illuminate\Console\Application(...)
[Symfony\Component\Console\Command\Command:name] => 'easypost:test'
[Symfony\Component\Console\Command\Command:processTitle] => null
[Symfony\Component\Console\Command\Command:aliases] => [...],
[Symfony\Component\Console\Command\Command:definition] => Symfony\Component\Console\Input\InputDefinition(...)
[Symfony\Component\Console\Command\Command:hidden] => false
[Symfony\Component\Console\Command\Command:help] => ''
[Symfony\Component\Console\Command\Command:description] => 'Command description'
[Symfony\Component\Console\Command\Command:fullDefinition] => Symfony\Component\Console\Input\InputDefinition(...)
[Symfony\Component\Console\Command\Command:ignoreValidationErrors] => false
[Symfony\Component\Console\Command\Command:code] => null
[Symfony\Component\Console\Command\Command:synopsis] => [...],
[Symfony\Component\Console\Command\Command:usages] => [...],
[Symfony\Component\Console\Command\Command:helperSet] => Symfony\Component\Console\Helper\HelperSet(...)
[*:laravel] => Illuminate\Foundation\Application#7(...)
[*:signature] => 'easypost:test'
[*:name] => 'easypost:test'
[*:description] => 'Command description'
[*:help] => null
[*:hidden] => false
[*:isolated] => false
[*:isolatedExitCode] => 0
[*:aliases] => null
[*:components] => Illuminate\Console\View\Components\Factory(...)
[*:input] => Symfony\Component\Console\Input\ArgvInput#8(...)
[*:output] => Illuminate\Console\OutputStyle#9(...)
[*:verbosity] => 32
[*:verbosityMap] => [...],
[*:signals] => null
),
1 => Symfony\Component\Console\Input\ArgvInput#8(...),
2 => Symfony\Component\Console\Output\ConsoleOutput#10(...),
],
],
22 => [
'file' => 'vendor/symfony/console/Application.php',
'line' => 175,
'function' => 'doRun',
'class' => 'Symfony\\Component\\Console\\Application',
'type' => '->',
'args' => [
0 => Symfony\Component\Console\Input\ArgvInput#8(...),
1 => Symfony\Component\Console\Output\ConsoleOutput#10(...),
],
],
23 => [
'file' => 'vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php',
'line' => 201,
'function' => 'run',
'class' => 'Symfony\\Component\\Console\\Application',
'type' => '->',
'args' => [
0 => Symfony\Component\Console\Input\ArgvInput#8(...),
1 => Symfony\Component\Console\Output\ConsoleOutput#10(...),
],
],
24 => [
'file' => 'artisan',
'line' => 37,
'function' => 'handle',
'class' => 'Illuminate\\Foundation\\Console\\Kernel',
'type' => '->',
'args' => [
0 => Symfony\Component\Console\Input\ArgvInput#8(...),
1 => Symfony\Component\Console\Output\ConsoleOutput#10(...),
],
],
]
[Exception:previous] => GuzzleHttp\Exception\ClientException#3(...)
[*:response] => Saloon\Http\Response#2(...)
[*:maxBodyLength] => 200
)
And the connector:
<?php
namespace App\Http\Integrations\EasyPost;
use Saloon\Helpers\OAuth2\OAuthConfig;
use Saloon\Http\Connector;
use Saloon\Traits\OAuth2\ClientCredentialsGrant;
use Saloon\Traits\Plugins\AcceptsJson;
use Saloon\Traits\Plugins\AlwaysThrowOnErrors;
use Saloon\Traits\Plugins\HasTimeout;
class EasyPostConnector extends Connector
{
use AcceptsJson;
use HasTimeout;
use ClientCredentialsGrant;
use AlwaysThrowOnErrors;
protected int $connectTimeout = 60;
protected int $requestTimeout = 120;
public function __construct(string $clientId, string $clientSecret)
{
$this->oauthConfig()->setClientId($clientId);
$this->oauthConfig()->setClientSecret($clientSecret);
}
/**
* The Base URL of the API
*/
public function resolveBaseUrl(): string
{
return config('services.easypost.base_url');
}
/**
* Default headers for every request
*/
protected function defaultHeaders(): array
{
return [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
];
}
/**
* Default HTTP client options
*/
protected function defaultConfig(): array
{
return [];
}
protected function defaultOauthConfig(): OAuthConfig
{
return OAuthConfig::make()
->setDefaultScopes(['connect/read:jobs', 'connect/submit:jobs', 'connect/read:sending-events'])
->setTokenEndpoint('https://auth.acc.connect.easypost.eu/oauth2/token');
}
}
Hmm. Very odd. The cURL command you use will implicitly use POST. But the error message from Guzzle states that it's also using POST. And the URLs look identical to me.
I can't really see anything wrong. Could you maybe remove the default headers in the Connector to test if that changes anything? It's a long shot, and technically has nothing to do with the error, but worth a shot.
I'm currently on my phone, but can test some stuff a bit later today. 🙂
Oh yes! removing the header did the trick, strange that the json is causing a 405 MethodNotAllowedException ...
Very strange! I'd expect it to result in a HTTP 400 since the endpoint itself can handle POST.
I guess you could create a custom OAuth Request that uses the expected headers.
I don't remember off the top of my head, but if the OAuth specification (or at least Client Credentials Grant) states that form data has to be used, then Saloon could enforce it. But with the possibility to override, since some APIs don't follow various specifications.
Hello,
I'm trying to implement a new API connection using OAuth2 but i'm facing a 405 error when trying to get the access token. The counter part of the API is correctly sending the data:
The code is the following:
The connector is containing the defaultOauthConfig
The error thrown:
Do you have any idea why the result is 405?