Guzzle HTTP send file stream throws error- "json_encode error: Type is not supported" - rest

I am using Guzzle client in my Laravel application to send a request to API endpoint along with a file. I am achieving this by creating a multipart data as follow-
$rid = $this->wsl->curlWSl('POST', '/throttle', [], [
'verify' => false,
'multipart' => [
[
'name' => 'csv',
'contents' => fopen($dest, 'rb')
],
[
'name' => 'name',
'contents' => $request->input('name')
],
[
'name' => 'description',
'contents' => $request->input('description')
],
[
'name' => 'header',
'contents' => '1'
]
]
]);
The curlWSL method I have defined as given below -
public function curlWSl(string $method, string $path, Array $headers = [], Array $data = null, Array $options = [])
{
$endPoint = $this->getUri() . $path;
if (!empty($headers)) {
$options['headers'] = $headers;
}
if ($method == 'GET' && $data) {
$endPoint .= http_build_query($data);
}
if ($method == 'POST') {
$options['json'] = $data;
}
try {
$response = $this->getClient()->request(
$method,
$endPoint,
$options
);
} catch (\Exception $ex) {
return ['statusCode'=>$ex->getCode(), 'errorMsg' => $ex->getMessage()];
}
return json_decode($response->getBody()) ?? (string)$response->getBody();
}
Doing this, throws me an exception -
InvalidArgumentException {#296 ▼
#message: "json_encode error: Type is not supported"
#code: 0
#file: "/var/www/html/vendor/guzzlehttp/guzzle/src/functions.php"
#line: 327
trace: {▶}
}
I am sure, this is because of fopen file stream because when I remove that, my request is received at the endpoint.
I am also looking for some help on how can I validate the request data at the API endpoint using laravel validators.
Your help is much appreciated.
Note: I am much looking to pass the file object and not only the file data, which I am able to do with file_get_contents.

I got it fixed.
The Guzzle don't process too many parameters to send as in request to the endpoint. As we are setting up $options['json'] = $data and this considers the data in json format which is actually not. So, I had to turn it to multipart instead of json to make things working. I had to modify the call as below -
$rid = $this->wsl->curlWSl('POST', '/throttle', [], [
[
'name' => 'csv',
'contents' => fopen($dest, 'rb')
],
[
'name' => 'name',
'contents' => $request->input('name')
],
[
'name' => 'description',
'contents' => $request->input('description')
],
[
'name' => 'header',
'contents' => '1'
]
]);
And in the method curlWSL, I modified the code as to accept the data as multipart-
public function curlWSl(string $method, string $path, Array $headers = [], Array $data = null, Array $options = [])
{
$endPoint = $this->getUri() . $path;
if (!empty($headers)) {
$options['headers'] = $headers;
}
if ($method == 'GET' && $data) {
$endPoint .= http_build_query($data);
}
if ($method == 'POST') {
$options['multipart'] = $data;
}
try {
$response = $this->getClient()->request(
$method,
$endPoint,
$options
);
} catch (\Exception $ex) {
return ['statusCode'=>$ex->getCode(), 'errorMsg' => $ex->getMessage()];
}
return json_decode($response->getBody()) ?? (string)$response->getBody();
}

Related

Argument 1 passed to GuzzleHttp\Client::send() must implement interface

hi I am new to laravel and I am trying to consume the api with laravel 8 I have a problem with my POST and I do not understand
public function storeEntreprise(Request $request){
$request->validate([
'name' => 'required',
'email' => 'required',
'phone_number'=>'required',
'address' => 'required',
'password' => 'required',
'password_confirmation' => 'required'
]);
$client = new Client();
$post = $request->all();
$url = "http://flexy-job.adsyst-solutions.com/api/entreprises-store";
$create = $client->request('POST', $url, [
'headers' => [
'Content-Type' => 'text/html; charset=UTF8',
],
'form-data' => [
'name' => $post['name'],
'email' => $post['email'],
'phone_number' => $post['phone_number'],
'address' => $post['address'],
'logo' => $post['logo'],
'password' => $post['password'],
'password_confirmation' => $post['password_confirmation']
]
]);
//dd($create->getBody());
echo $create->getStatusCode();
//echo $create->getHeader('Content-Type');
echo $create->getBody();
$response = $client->send($create);
return redirect()->back();
}
Can you help me please
You calling (accidentally?) $response = $client->send($create); where $create is response of API request you made ($create = $client->request('POST', $url, ...).
So PHP reports you that you can't pass ResponseInterface where RequestInterface required.
Also, you echo's body of response, and return redirect response at same time. So browser will not show you API response (because of instance back redirect).

Use CakePHP Http Client with Magento2 rest API search criteria

I'm trying to send a GET request to a local Magento2 rest API to get all the orders after a certain time. I'm following http://devdocs.magento.com/guides/v2.1/howdoi/webapi/search-criteria.html#simple-search-using-a-timestamp. I'm using CakePHP 3.4's Http Client (https://book.cakephp.org/3.0/en/core-libraries/httpclient.html) and have successfully integrated with Magento using Oauth1 and have no problems with simpler GET requests like http://www.magento.dev.com/rest/V1/stockItems/:productSku. It is a problem with passing the search criteria. The response is always a 401 Invalid Signature.
Using Postman, I can get a valid response to http://www.magento.dev.com/rest/V1/orders?searchCriteria[filter_groups][0][filters][0][field]=created_at&searchCriteria[filter_groups][0][filters][0][value]=2016-07-01 00:00:00&searchCriteria[filter_groups][0][filters][0][condition_type]=gt
This is what I have so far/how I'm sending the request:
In Model/Table/OrdersTable.php:
public function importNewOrders(\App\Model\Entity\OauthIntegration $integrationDetails)
{
$this->OauthIntegrations = TableRegistry::get('OauthIntegrations');
$this->Orders = TableRegistry::get('Orders');
$timeCutOff = '2015-01-01 00:00:00';
$search = [
'searchCriteria' => [
'filterGroups' => [
0 => [
'filters' => [
0 => [
'field' => 'created_at',
'value' => $timeCutOff,
'condition_type' => 'gt'
]
]
]
]
]
];
// 'searchCriteria[filter_groups][0][filters][0][field]' => 'created_at',
// 'searchCriteria[filter_groups][0][filters][0][value]' => $timeCutOff,
// 'searchCriteria[filter_groups][0][filters][0][condition_type]' => 'gt'
$action = '/V1/orders';
$type = "GET";
$response = $this->OauthIntegrations->sendRequest(
$integrationDetails,
$action,
$type,
'',
$search);
Log::write('debug', $response->body());
return $response;
}
and in Model\Table\OauthIntegrationsTable.php:
public function sendRequest(\App\Model\Entity\OauthIntegration $integrationDetails,
string $action, string $method = "GET", string $data = '', array $search = null)
{
$http = new Client([
'auth' => [
'type' => 'oauth',
'consumerKey' => $integrationDetails->oauth_consumer_key,
'consumerSecret' => $integrationDetails->oauth_consumer_secret,
'token' => $integrationDetails->oauth_token,
'tokenSecret' => $integrationDetails->oauth_token_secret
]
]);
$url = $integrationDetails->store_base_url . 'rest' . $action;
if ($method == 'GET'){
if (!isset($search)){
$search = [];
}
$response = $http->get($url, $search, []);
} else if ($method == 'POST'){
$response = $http->post($url, $data, [
'type' => 'json',
]);
} else if($method == 'PUT'){
$response = $http->put($url, $data, [
'type' => 'json',
]);
}
Log::write('debug', 'url: ' . $url . ' and status code: ' . $response->getStatusCode());
return $response;
}
and this is the error (I'm hoping) is the cause of the Invalid Signature response:
2017-03-28 10:07:01 Notice: Notice (8): Array to string conversion in [/var/www/cakephp/html/beacon/vendor/cakephp/cakephp/src/Http/Client/Auth/Oauth.php, line 315]
Trace:
Cake\Error\BaseErrorHandler::handleError() - CORE/src/Error/BaseErrorHandler.php, line 153
Cake\Http\Client\Auth\Oauth::_normalizedParams() - CORE/src/Http/Client/Auth/Oauth.php, line 315
Cake\Http\Client\Auth\Oauth::baseString() - CORE/src/Http/Client/Auth/Oauth.php, line 246
Cake\Http\Client\Auth\Oauth::_hmacSha1() - CORE/src/Http/Client/Auth/Oauth.php, line 143
Cake\Http\Client\Auth\Oauth::authentication() - CORE/src/Http/Client/Auth/Oauth.php, line 61
Cake\Http\Client::_addAuthentication() - CORE/src/Http/Client.php, line 501
Cake\Http\Client::_createRequest() - CORE/src/Http/Client.php, line 448
Cake\Http\Client::_doRequest() - CORE/src/Http/Client.php, line 341
Cake\Http\Client::get() - CORE/src/Http/Client.php, line 211
App\Model\Table\OauthIntegrationsTable::sendRequest() - APP/Model/Table/OauthIntegrationsTable.php, line 134
App\Model\Table\OrdersTable::importNewOrders() - APP/Model/Table/OrdersTable.php, line 672
App\Shell\MagentoShell::main() - APP/Shell/MagentoShell.php, line 36
Cake\Console\Shell::runCommand() - CORE/src/Console/Shell.php, line 472
Cake\Console\ShellDispatcher::_dispatch() - CORE/src/Console/ShellDispatcher.php, line 227
Cake\Console\ShellDispatcher::dispatch() - CORE/src/Console/ShellDispatcher.php, line 182
Cake\Console\ShellDispatcher::run() - CORE/src/Console/ShellDispatcher.php, line 128
[main] - ROOT/bin/cake.php, line 33
Code from Http\Client\Oauth.php where error occurs:
$pairs = [];
foreach ($args as $k => $val) {
if (is_array($val)) {
sort($val, SORT_STRING);
Log::write('debug', 'about to go through foreach($val as $nestedVal)');
foreach ($val as $nestedVal) {
Log::write('debug', $nestedVal);
$pairs[] = "$k=$nestedVal"; // <<< HERE
}
} else {
$pairs[] = "$k=$val";
}
}
debugging from above results in:
2017-03-28 10:07:01 Debug: about to go through foreach($val as $nestedVal)
2017-03-28 10:07:01 Debug: Array
(
[0] => Array
(
[filters] => Array
(
[0] => Array
(
[field] => created_at
[value] => 2015-01-01 00:00:00
[condition_type] => gt
)
)
)
)
In summary, is it possible to pass a multi-dimensional array to the 2nd parameter in a get request using Cake's Http Client?
// Is it possible to replace ['q' => 'widget'] with a multi-dimensional array??
$response = $http->get('http://example.com/search', ['q' => 'widget']);
If not, what would be the best way to use Cake's Http Client to send GET request to: http://www.magento.dev.com/rest/V1/orders?searchCriteria[filter_groups][0][filters][0][field]=created_at&searchCriteria[filter_groups][0][filters][0][value]=2016-07-01 00:00:00&searchCriteria[filter_groups][0][filters][0][condition_type]=gt ?
Thanks in advance!!!
Possible bug
This may be considered as a possible bug. I don't think the OAuth specs take this PHP style bracket stuff in URLs into account, and therefore sorting/encoding the parameters is limited to flat key=value sets, ie a key would be
searchCriteria[filter_groups][0][filters][0][field]
and the value would be
created_at
The CakePHP OAuth adapter however parses the requests query string into a possibly deeply nested array structure, which will then fail, as it doesn't handle that case.
I'd suggest that you report this as a possible bug. Further problems may occour as encoding seems to be ment to be applied before sorting, where in the CakePHP implementation, additonal parameter encoding is applied after sorting (that may actually be fine though, I'm not sure).
Try a custom OAuth adapter as a workaround
Until this is being fixed/enhanced, you could use a custom OAuth adapter that handles things "properly" (whatever that means in this context). Here's a quick and dirty example (works for me with the Magento API).
Create src/Http/Client/Auth/AppOAuth.php
<?php
namespace App\Http\Client\Auth;
use Cake\Http\Client\Auth\Oauth;
class AppOAuth extends Oauth
{
protected function _normalizedParams($request, $oauthValues)
{
$query = parse_url($request->url(), PHP_URL_QUERY);
parse_str($query, $queryArgs);
$post = [];
$body = $request->body();
if (is_string($body) &&
$request->getHeaderLine('content-type') === 'application/x-www-form-urlencoded'
) {
parse_str($body, $post);
}
if (is_array($body)) {
$post = $body;
}
$args = array_merge($queryArgs, $oauthValues, $post);
$query = http_build_query($args);
$args = [];
foreach (explode('&', $query) as $value) {
$pair = explode('=', $value, 2);
$args[] =
rawurlencode(rawurldecode($pair[0])) .
'=' .
rawurlencode(rawurldecode($pair[1]));
}
usort($args, 'strcmp');
return implode('&', $args);
}
}
Compare to \Cake\Http\Client\Auth\Oauth::_normalizedParams()
Use it by specifying the classname in the type option for your client instance:
'type' => 'AppOAuth',
ps
shouldn't it be filter_groups instead of filterGroups in your $search array?

how to pass variables "$tid, $id" into raw function?

when i call $id and $tid in raw function to fetch some data from sub document of mongodb collection it show me an error these two variables are undefined($tid,$id)?
<?php
$id=IntValue;
$tId=IntValue;
if($tId==0)
{
$maxPlayers=0;
}
else
{
$result = DB::collection('PrizeDistributionTemplate')->raw(function($collection)
{
return $collection->aggregate(array(
array(
'$match' => array( 'id' => $id,'templates.tId' => $tid)
),
array( '$unwind' => '$templates' ),
array(
'$match' => array( 'id' => $id,'templates.tId' => $tid)
),
));
});
$result=json_decode(json_encode($result),true);
$maxPlayers=$result['result'][0]['templates']['maxPlayers'];
$maxPlayers=intval($maxPlayers)+2;
}
?>
When you use a callback function in PHP, the function as it own scope and can't access variables from outside of it's scope.
$foo = true;
DB::collection('something')->raw(function ($collection) {
echo $foo;// $foo is undefined here, this create an error
});
echo $foo;// here it work
But you can feed your callback with variables using the PHP use keyword:
$foo = true;
DB::collection('something')->raw(function ($collection) use ($foo) {
echo $foo;// now it works
});
It will work great with bulk addition, this way you just need to create on array and pass it.
$temp = [
[
'item' => "envelopes"
],
[
'item' => "envelopesas"
],
[
'item' => "lala"
]
];
$userData = DB::table('log') - > raw(function ($collection) use($temp)
{
return $collection - > insertMany($temp);
});

Drupal7 REST: I am not able to perform POST and PUT methods. Error is :Not Acceptable : Node type is required, Code:406?

I'm using drupal7. my drupal_http_request() for get and delete are working fine for authenticated users, but the post and put methods are not working.
The error is :Not Acceptable : Node type is required, and http error code is :406. My code is below:
function ws_form_post_auth() {
$base_url = 'http://localhost/drupalws/api/v1';
$data = array(
'username' => 'student1',
'password' => 'welcome',
);
$data = http_build_query($data, '', '&');
$options = array(
'headers' => array(
'Accept' => 'application/json',
),
'method' => 'POST',
'data' => $data
);
$response = drupal_http_request($base_url . '/user/login', $options);
$data = json_decode($response->data);
// Check if login was successful
if ($response->code == 200) {
$options['headers']['Cookie'] = $data->session_name . '=' . $data->sessid;
$options['headers']['X-CSRF-Token'] = $data->token;
$data = array(
'title' => 'First forum post',
'type'=> 'forum',
'body'=> array(
'und'=>array(
0=> array(
'value'=>'This is my first forum post via httprequest.'
)
)
)
);
$data = json_encode($data);
$options['data'] = $data;
$options['method'] = 'POST';
$response = drupal_http_request($base_url . '/node', $options);
return $response->status_message;
}
return $response->status_message;
}
I got the solution for my issue,I just missed a Content-Type in Headers.
[....]
if ($response->code == 200) {
$options['headers']['Cookie'] = $data->session_name . '=' . $data->sessid;
$options['headers']['X-CSRF-Token'] = $data->token;
$options['headers']['Content-Type'] = 'application/json';
[....]

How to populate Yii2 Autocomplete with AJAX call

I am trying to switch to Yii2 from Yii 1.1. This was source attribute of TextAreaJuiAutoComplete widget
'source'=>"js:function(request, response) {
$.getJSON('".$url"', {
term: extractLast(request.term)
}, response);
}",
This is not working in Yii2 with yii\jui\AutoComplete anymore. Can anyone give me a hint what is the cause? Underlying JavaScript objects should be the same.
If I put following code it works, but I want to use ajax calls instead.
'source' => [ "c++", "java", "php", "coldfusion", "javascript", "asp", "ruby" ],
Try this:
use yii\web\JsExpression;
.....
.....
'source'=>new JsExpression("function(request, response) {
$.getJSON('".$url."', {
term: request.term
}, response);
}"),
Try this:
AutoComplete::widget([
'name'=>'myacfield',
'clientOptions' => [
'source' => Url::to(['autocomplete']),
'minLength'=>'2',
],
'options'=>[
'class' => 'form-control'
]
]);
But your AutoComplete action must return a one dimensional array like
...
$rs = Yii::$app->db->createCommand($sql)->queryAll();
$row_set = [];
foreach ($rs as $row)
{
$row_set[] = $row['name']; //build an array
}
echo json_encode($row_set); //format the array into json data
Examle with like.
Controller:
public function actionSearch($term)
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$rs = Cure::find()->where(['like', 'name_uz', $term])->all();
if($rs !=null){
$row_set = [];
foreach ($rs as $row)
{
$row_set[] = $row->name_uz; //build an array
}
return $row_set;
}else{
false;
}
}
In view:
<? use yii\jui\AutoComplete;?>
<?= AutoComplete::widget([
'model' => $model,
'attribute' => 'country',
'options' => ['class' => 'form-control'],
'clientOptions' => [
'source' => Url::to(['cure/search']),
'minLength'=>'2',
],
]); ?>