Comments (8)
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.
from saloon.
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?
from saloon.
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...
from saloon.
I've tried the new ->debug() with no luck...
The result is the same, seems like saloon didn't execute the request.
from saloon.
@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.
from saloon.
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');
}
}
from saloon.
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. 🙂
from saloon.
Oh yes! removing the header did the trick, strange that the json is causing a 405 MethodNotAllowedException ...
from saloon.
Related Issues (20)
- Paginator Next Request URL help HOT 5
- Create commands not respecting config file HOT 4
- Dots in parameters get converted to underscores because of parse_str HOT 7
- Refresh token with Client Credentials Grant HOT 5
- Faking a response - failed to match url HOT 2
- Cannot Have Request with `headers` HOT 4
- Skipping authentication when request is cached HOT 5
- Skipping AuthenticatePendingRequest when fetching a new accessToken using OAuth2 plugins HOT 3
- Missing methods updates for MockResponse HOT 1
- ERROR There are no commands defined in the "saloon" namespace. HOT 1
- Rate Limit Plugin: Issue with 'allow(1)->everySeconds(1)->sleep()' Exceeding One Request per Second
- Inteliphense cannot recognize the merge() method on request HOT 3
- Connector default headers overwrite request body headers
- Remove authentication on a specific request HOT 1
- Some properties are removed from the response body
- Using non-standard HTTP methods HOT 2
- Generating Documentation Files for GPT on ChatGPT
- Tests using Fixtures with custom Authenticators record incorrect response HOT 1
- async request with multi connector
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from saloon.