9

It is either not being sent, or not being received correctly. Using curl direct from the command line (using the -d option) or from PHP (using CURLOPT_POSTFIELDS) does work.

I start with a PSR-7 request:

$request = GuzzleHttp\Psr7\Request('POST', $url);

I add authentication header, which authenticates against the API correctly:

$request = $request->withHeader('Authorization', 'Bearer ' . $accessToken);

Then I add the request body:

// The parameter for the API function
$body = \GuzzleHttp\Psr7\stream_for('args=dot');
$request = $request->withBody($body);

I can send the message to the API:

$client = new \GuzzleHttp\Client();
$response = $client->send($request, ['timeout' => 2]);

The response I get back indicates that the "args" parameter was simply not seen by the API. I have tried moving the authentication token to the args:

'args=dot&access_token=123456789'

This should work, and does work with curl from the command line (-d access_token=123456789) but the API fails to see that parameter also when sending cia curl (6.x) as above.

I can see the message does contain the body:

var_dump((string)$request->getBody());
// string(8) "args=dot"
// The "=" is NOT URL-encoded in any way.

So what could be going wrong here? Are the parameters not being sent, or are they being sent in the wrong format (maybe '=' is being encoded?), or is perhaps the wrong content-type being used? It is difficult to see what is being sent "on the wire" when using Guzzle, since the HTTP message is formatted and sent many layer deep.

Edit: Calling up a local test script instead of the remote API, I get this raw message detail:

POST
CONNECTION: close
CONTENT-LENGTH: 62
HOST: acadweb.co.uk
USER-AGENT: GuzzleHttp/6.1.1 curl/7.19.7 PHP/5.5.9

args=dot&access_token=5e09d638965288937dfa0ca36366c9f8a44d4f3e

So it looks like the body is being sent, so I guess something else is missing to tell the remote API how to interpret that body.

Edit: the command-line curl that does work, sent to the same test script, gives me two additional header fields in the request:

CONTENT-TYPE: application/x-www-form-urlencoded
ACCEPT: */*

I'm going to guess it is the content-type header which is missing from the Guzzle request which is the source of the problem. So is this a Guzzle bug? Should it not always sent a Content-Type, based on the assumptions it makes that are listed in the documentation?

Jason
  • 2,506
  • 4
  • 31
  • 43

2 Answers2

15

The Content-Type header was the issue. Normally, Guzzle will hold your hand and insert headers it deems necessary, and makes a good guess at the Content-Type based on what you have given it, and how you have given it.

With Guzzle's PSR-7 messages, none of that hand-holding is done. It strictly leaves all the headers for you to handle. So when adding POST parameters to a PSR-7 Request, you must explicitly set the Content-Type:

$params = ['Foo' => 'Bar'];
$body = \GuzzleHttp\Psr7\stream_for(http_build_query($params));
$request = $request->withBody($body);
$request = $request->withHeader('Content-Type', 'application/x-www-form-urlencoded');

The ability to pass in the params as an array and to leave Guzzle to work out the rest, does not apply to Guzzle's PSR-7 implementation. It's a bit clumsy, as you need to serialise the POST parameters into a HTTP query string, and then stick that into a stream, but there you have it. There may be an easier way to handle this (e.g. a wrapper class I'm not aware of), and I'll wait and see if any come up before accepting this answer.

Be aware also that if constructing a multipart/form-data Request message, you need to add the boundary string to the Content-Type:

$request = $request->withHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary);

Where $boundary can be something like uniq() and is used in construction the multipart body.

Jason
  • 2,506
  • 4
  • 31
  • 43
  • 1
    This line: `$body = new \GuzzleHttp\Psr7\stream_for(http_build_query($params));` should actually be `$body = \GuzzleHttp\Psr7\stream_for(http_build_query($params));` No `new` is required – lolsky May 21 '20 at 20:06
  • `stream_for()` is indeed a function. It can be imported with `use function GuzzleHttp\Psr7\stream_for`, and can be aliased too, if it clashes with another global function of the same name. Corrected in answer, thanks. – Jason May 23 '20 at 09:48
15

The GuzzleHttp\Client provides all necessary wrapping.

$response = $client->post(
    $uri,
    [
        'auth' => [null, 'Bearer ' . $token],
        'form_params' => $parameters,
]);

Documentation available Guzzle Request Options

Edit: However, if your requests are being used within GuzzleHttp\Pool then, you can simply everything into the following:

$request = new GuzzleHttp\Psr7\Request(
    'POST',
    $uri,
    [
       'Authorization' => 'Bearer ' . $token,
       'Content-Type' => 'application/x-www-form-urlencoded'

    ],
    http_build_query($form_params, null, '&')
);
Martti Laine
  • 11,524
  • 19
  • 62
  • 100
Shaun Bramley
  • 1,869
  • 8
  • 14
  • So the Guzzle *client* will put the correct `Content-Type` headers in, provided you give it the raw data in the right way (`form_params` is the trigger here, I guess). Guzzle will *not* add any headers without being given them explicitly when constructing a PSR-7 request message. My aim here was to construct a PSR-7 message that could be sent through any HTTP client supporting PSR-7 messages, as much for the purity of just seeing that it can be done in as portable a way as possible, so using only PSR-7 methods on the helper Guzzle/PSR-7 package. – Jason Jan 09 '16 at 14:10
  • There should probably be a space after `Bearer`, within the quotes. – Jason Jan 09 '16 at 14:17
  • Correct, the client will automatically insert the proper content-type header and the authentication header when using the 'auth' and 'form_params' request options. It is something one must remember to do when manually building the requests for usage with other PSR7 compliant packages. – Shaun Bramley Jan 09 '16 at 19:26
  • For post request `'Content-Type' => 'application/json'` and 4th parameter as json_encode($data) in Request – vinay kumar reddy Aug 29 '17 at 13:20