Guzzle HTTP request transforms from POST to GET - php

I have this very weird thing going on when trying to make post to an external API, I try to make a POST request to the URL but Guzzle make a GET request instead (which is a legal action on this API but returns something different).
Here is the code:
$request = $this->client->createRequest('POST', 'sessions', [
'json' => [
'agent_id' => $agentId,
'url' => $url
],
'query' => [
'api_key' => $this->apiKey
]
]);
echo $request->getMethod(); // comes out as POST
$response = $this->client->send($request);
echo $request->getMethod(); // suddenly becomes GET
Same thing happens when I use $this-client->post(…)
I really have no idea what to do next.

I run into the same problem.
the reason is that Guzzle Changes the Request-Method to 'GET' when there is a location Redirect with code 301 or 302.
I found the 'Problem-Code' in the RedirectMiddleware.php.
But when you see the if-condition you can disable this behavior by adding 'allow_redirects'=>['strict'=>true] to your options.
After finding this option, I discovered that the option is listed in the Guzzle Options Documentation
So yust rewrite your createRequest like this:
$request = $this->client->createRequest('POST', 'sessions', [
'json' => [
'agent_id' => $agentId,
'url' => $url
],
'query' => [
'api_key' => $this->apiKey
],
'allow_redirects'=> ['strict'=>true]
]);
And it should stay Method POST after the redirect.

You're probably getting a 3xx status code so that that the Redirect subscriber kicks in (redirect is enabled by default). From the docs:
[...] Pass an associative array containing the ‘max’ key to specify
the maximum number of redirects and optionally provide a ‘strict’ key
value to specify whether or not to use strict RFC compliant redirects
(meaning redirect POST requests with POST requests vs. doing what
most browsers do which is redirect POST requests with GET requests).
//edit Just saw you kinda answered that yourself in the question comments - still leaving this answer online as it provides some context.

Try to change the key 'query' to 'body'.

Please switch query to form_params. In Guzzle 6 it works.

Related

Trying to use bulksms.com API using guzzle in Laravel but returning errors

I have been trying to use guzzle for sending bulk sms from bulksms.com and it is returning this error,
guzzlehttp\exception\clientexception client error: post
https://api.bulksms.com/v1/messages resulted in a 401 full
authentication is required to access this resource response: : "type"
"https://developer.bulksms.com/json/v1/errors#authentication-failed
My code
$client = new Client([
'base_uri'=>'https://www.bulksms.com/',
'timeout'=>'900.0'
]);
//$result = $client->post('', [
// 'form_params' => [
// 'sample-form-data' => 'value'
// ]
//]);
$result = $client->request('POST','https://api.bulksms.com/v1/messages', [
'form_params' => [
'username' => 'username',
'password' => '****',
'sender' => 'my appname',
'recipients' => '+5555555555',
'message' => 'Testing message',
]
]);
Other people have already pointed you towards using authentication correctly, and using JSON as the format of your request. Additionally, you're using the wrong variable names. For example, the documentation uses the variable name to, and you have used recipients instead (maybe you copied and pasted that code from somewhere else?).
The documentation has a PHP code sample the uses curl, at: https://www.bulksms.com/developer/json/v1/#tag/Message - why not use that as a basis, and then convert it to a working Guzzle request, as a starting point?
Did you have a look at the Authentication section in the API docs?
You should authenticate with the API using HTTP Basic Auth.

Different method called when using Laravel form request validation

I have a controller (API\Fields) with a method named store, the route to that method is set up like this:
POST /api/templates/{template}/fields -> API\Fields#store
Everything worked properly until I created a very simple form request validation class with the following rules (This is the only thing I changed besides the return value for the authorize method):
return [
'name' => ['required', 'alpha_num'],
'coordinates' => ['required', 'json'],
'type' => ['required', BaseField::RULE],
'page' => ['required', 'numeric'],
'readonly' => ['sometimes', 'boolean'],
'required' => ['sometimes', 'boolean']
];
After I created the class, I simply changed the request class from Request, to CreateFieldsRequest and it messed pretty much the whole routing for that route up. Instead of calling store, Laravel seems to be calling index. When I restore CreateFieldsRequest back to just the Request class, it behaves as it should again.
I haven't been able to find any information on this topic, and I've verified over and over that I don't have some sort of incorrect routing or redirections on any of the related classes.
Any help or guidance with this would be greatly appreciated, thank you!
When I run the request through the Chrome developer console as a POST request, Laravel kicks it back as a "GET" request, not sure why.
A FormRequest that fails validation issues a redirect. It's the default behavior.
If you issue an AJAX request, or request a JSON response with an Accept header, it'll respond with a JSON list of validation errors and a 422 HTTP code instead.
After running a very simple test I realized that this seems to be an issue with Postman. If you are experiencing this issue stick to adding a _method=POST parameter on your POST body, or simply use XHR or a different API testing tool.
Edit: After further testing I realized the issue had not been fixed. When I run the request through the Chrome developer console as a POST request, Laravel kicks it back as a "GET" request, not sure why.

Laravel route returns wrong route [duplicate]

I'm accessing an api in my own project, but now I'm having problem with the route function, after dispatching the request with app()->handle($req), route function generate a different url
$req = Request::create('/api/auth/login', 'POST', [
"user" => $request->user,
"password" => $request->password,
]);
$redirect = route('home'); // http://127.0.0.1:8000/home
$res = app()->handle($req);
$redirect = route('home'); // http://localhost/home
What did I miss?
Request::create() is a method inherited from Symfony's HTTP Request class. When called, if you do not pass in any $_SERVER details, it will use reasonable defaults.
The UrlGenerator Laravel class uses the current Request to determine the fully-qualified domain name when calling functions such as route(). Since you did not tell the Request what the current domain is, it is reverting to localhost.
If you're in an environment where $_SERVER is populated with the proper information, you can pass it to the proper parameter:
Request::create(
'/api/auth/login',
'POST',
[
'user' => $request->user,
'password' => $request->password,
],
[], // cookies
[], // files
$_SERVER
);
Other potential solutions that may fit well:
Use Request::createFromGlobals() to populate a request with PHP's superglobals such as $_POST, $_SERVER, etc., then modify the parts that you want to change.
If the $request variable already holds a Laravel Request instance, you can call $request->duplicate(). And again, modify as needed.

Laravel route url changing after app()->handle() function

I'm accessing an api in my own project, but now I'm having problem with the route function, after dispatching the request with app()->handle($req), route function generate a different url
$req = Request::create('/api/auth/login', 'POST', [
"user" => $request->user,
"password" => $request->password,
]);
$redirect = route('home'); // http://127.0.0.1:8000/home
$res = app()->handle($req);
$redirect = route('home'); // http://localhost/home
What did I miss?
Request::create() is a method inherited from Symfony's HTTP Request class. When called, if you do not pass in any $_SERVER details, it will use reasonable defaults.
The UrlGenerator Laravel class uses the current Request to determine the fully-qualified domain name when calling functions such as route(). Since you did not tell the Request what the current domain is, it is reverting to localhost.
If you're in an environment where $_SERVER is populated with the proper information, you can pass it to the proper parameter:
Request::create(
'/api/auth/login',
'POST',
[
'user' => $request->user,
'password' => $request->password,
],
[], // cookies
[], // files
$_SERVER
);
Other potential solutions that may fit well:
Use Request::createFromGlobals() to populate a request with PHP's superglobals such as $_POST, $_SERVER, etc., then modify the parts that you want to change.
If the $request variable already holds a Laravel Request instance, you can call $request->duplicate(). And again, modify as needed.

How do I do HTTP basic authentication using Guzzle?

I want to do basic access authentication using Guzzle and I am very new to programming. I have no clue what to do. I tried to do this using curl but my environment requires using guzzle.
If you're using Guzzle 5.0 or newer, the docs say that basic auth is specified using the auth parameter:
$client = new GuzzleHttp\Client();
$response = $client->get('http://www.server.com/endpoint', [
'auth' => [
'username',
'password'
]
]);
Please note that the syntax is different if you're using Guzzle 3.0 or earlier. The constructor is different, and you also need to explicitly use the send method on a request to get a response:
$client = new Guzzle\Http\Client();
$request = $client->get('http://www.server.com/endpoint');
$request->setAuth('username', 'password');
$response = $request->send();
A brief addendum
In response to #Matthwew-Knill, yes, you can set a default authorization and implicitly have Guzzle send it in each further request. #Nick's answer is on point. The client constructor takes every parameter you could think of and then some.
Another approach, if you want to get creative, would be to instantiate the client passing it default headers to send on every further request. Simple auth is, after all, an Authorization header. It's computed as:
$client = new Client([
'headers'=>[
'Authorization'=> Basic base64_encode(<username>:<password>)
]
]);
Last but not least please note that filling a simple auth dialog happens only once (upon the virst visit of a given session). This is usually achieved by setting a cookie on the visitor's browser. That cookie in turn contains enough info for the server to identify its matching active session.
Usually, Guzzle requests are stateless, but you can configure Guzzle with a middleware chain to either modify request or responses, for debug purposes and, for this use case, to remember cookies, thus becoming partially stateful.
Please check the detailed procedure in Guzzle Docs. The important thing is that, by instantiating the client with a cookiejar middleware, therefore having the client include it from then on, the first request will remember the server's set-cookie header, and will send it as every further cookie header, making the server recognize the client as a logged in user. Of course, you could also inspect the first response's headers yourself and send its value from then on.
There might be other ways, but I can't think of another right now.
In additional to #amenadiel answer. Sometimes handy specify auth parameters in constructor:
$client = new Client([
'auth' => ['username', 'password'],
]);
Then every request will use this default auth parameters.
This dint work when I used Guzzlev6 and used the advice from #amenadiel. When you use curl, your syntax would look something like
curl -u someone#gmail.com:password http://service.com
behind the scenes it actually takes the "someone#gmail.com:password" bit, base64 encodes it and sends the request with an "Authorization" Header with the encoded value. For this example, that will be:
Authorization: Basic c29tZW9uZUBnbWFpbC5jb206cGFzc3dvcmQ=
Advice from #amenadiel appended an "auth: username,password" header and hence, my authentication kept failing. To achieve this successfully, just craft the header when you are instantiating a Guzzle Client request, i.e
$client = new GuzzleHttp\Client();
$credentials = base64_encode('someone#gmail.com:password');
$response = $client->get('http://www.server.com/endpoint', [
'Authorization' => ['Basic '.$credentials]
]);
That would append the header as curl would, and whatever service you are trying to connect to will stop yelling at you,
Cheers.
According to the Guzzle 6 documentation, you can do a request with basic authorization as simple as this:
$client = new Client();
$response = $client->request(
'POST', /*instead of POST, you can use GET, PUT, DELETE, etc*/
$url,
[
'auth' => ['username', 'password'] /*if you don't need to use a password, just leave it null*/
]
);
echo $response->getBody();
NOTE: You don't need to use base64_encode() at all because it already does it before the request.
I've tested and it works :)
See more at: Guzzle 6 Documentation
$response = $client->request( 'GET', 'your_url', [
'auth' => [
'your_username',
'your_password'
],
'headers' => [
'if you want to pass something in the headers'
]
]
);
You can also configure the auth params when instantiating the client instead of adding it to each request:
$this->client = new \GuzzleHttp\Client([
'base_uri' => $this->endpoint,
'headers' => [
'Authorization' => ['Basic '.base64_encode($this->username.':'.$this->password)],
],
]);
Here are the various doc links for Guzzle 6:
Creating a Client
Request Options
Auth Request Options
According to what #bourgeois247 said about base64 encoding, the following worked perfectly for me on Guzzle 6:
$client = new Client();
$credentials = base64_encode('username:password');
$response = $client->post('url',
[
'headers' => [
'Authorization' => 'Basic ' . $credentials,
],
]);
If you use it with symfony, you can also define it in your configuration file (config/packages/eight_points_guzzle.yaml for symfony4 or flex or config.yml for the other version)
In your configuration file :
eight_points_guzzle:
clients:
your_service:
# Write here the host where to do requests
base_url: "yourURL"
options:
timeout: 30
auth:
- yourLogin # login
- yourPassword # password
plugin: ~
Then, in your service, controller, etc....
$client = $this->getContainer()->get('eight_points_guzzle.client.your_service');
$response = $client->get('yourRoute');
See : https://packagist.org/packages/eightpoints/guzzle-bundle

Categories