GuzzleHttp - Get JSON encoded body - json

I created an API client for my company to fetch orders from our distributors. I need to acknowledge download of orders back to them with a PUT. The PUT is working properly but I get an error on their confirmation of my acknowledgement.
Using Postman, I get a JSON body message back.
When I PUT a acknowledgement back, I get the following error:
Type error: Argument 1 passed to GuzzleHttp\Client::send() must
implement interface Psr\Http\Message\RequestInterface, instance of
GuzzleHttp\Psr7\Response given, called in
/var/www/orders/app/Http/Controllers/edi/OrderController.php on line 86
This is line 86:
$response = $client->send($apirequest);
The relevant code:
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Client as GuzzleHttpClient;
use GuzzleHttp\Psr7\Stream;
use Illuminate\Support\Facades\Input;
use Response;
use XmlParser;
use Psr\Http\Message\RequestInterface;
public function orderConfirm()
{
$uri = config('services.orders.orderack');
$formdata = Input::all();
$orders = Input::get('orders');
try {
$client = new GuzzleHttpClient([
'headers'=> [
'Authorization' => '$user',
'ContractID' => '$contract',
'Content-Type' => 'application/json']
]);
$apirequest = $client->request('PUT', $uri,
['body' => json_encode(
[
$orders
]
)]
);
$response = $client->send($apirequest);
$contents = (string) $response->getBody();
return $contents;
}
catch (RequestException $ex) {
//Exception Handling
echo $ex;
}
Output from Postman was:
"Number of Orders Acknowledged: 1"
from other posts on SO, this:
$contents = (string) $response->getBody();
is the way to get the body and other people fixed their problems, but it's not working for me.
Obviously I'm still missing something here!

Calling $client->request() actually does the request (which is why it's returning an instance of GuzzleHttp\Psr7\Response) instead of building a request object to send later. You don't need to tell the client to send anything because it's already been sent; you just need to set the $response variable to the value of the call to $client->request().
This can be seen in the Body example in their PSR7 documentation.
$response = $client->request('GET', 'http://httpbin.org/get');
To build a request object manually, you will have to create an instance of GuzzleHttp\Psr7\Request using its constructor, as documented under Requests.
// Create a request using a completely custom HTTP method
$request = new \GuzzleHttp\Psr7\Request('MOVE', 'http://httpbin.org/move');
echo $request->getMethod();
// MOVE

Related

Json::decode returns NULL

I have problem with Json::decode. I'm using this code:
use Drupal\Component\Serialization\Json;
$client = \Drupal::httpClient();
$request = $client->post($rest_url, [
'form_params' => [
'id' => $rest_id,
],
]);
$response = Json::decode($request->getBody());
to get JSON from some server but it returns NULL. Of course this is just a part of the code (without try, catch...)
$request->getBody() return is ok, but in Json::decode I'm still getting NULL.
The only thing I noticed is that in Postman, when I look at raw body content, I see some empty lines at the beginning of the JSON (like return on keyboard when typing), but I checked JSON as it is on JSONLint and it's valid.
Any idea what is the problem?
I'm not familiar with Drupal's JSON serializer, but try to force response body conversation to a string.
$response = Json::decode($request->getBody()->getContents());
Guzzle return a Stream object from getBody(), it could be the issue.

Yii Restful API will create on POST but not populate any variables

Hello I am currently trying to use POSTMAN to test an early API that is based of of this post
http://www.yiiframework.com/wiki/175/how-to-create-a-rest-api/
I am having an error when trying to either submit variables via POST in php url style or even sending an object. The response from the API comes back 200 and it creates a new entry into the database, but unfortunately it will not take any info from the post variables or jason object, it just comes up null. It seems now that that code is just looking through $_POST variables to and trying to match them to a model variable and if so, it should update it save it, However when i try to send through url parameters in POSTMAN or even change content type json and send raw json object I seem to have no luck with it.
Also I really only need it to decode a jason object and not post parameters so maybe that is where I will start by removing the $post loop and working on retrieving a JSON object instead. Thanks for any help!
public function actionCreate()
{
switch($_GET['model'])
{
// Get an instance of the respective model
case 'event':
$model = new Event;
break;
case 'media':
$model = new Media;
break;
case 'comment':
$model = new Comment;
break;
default:
$this->_sendResponse(501,
sprintf('Mode <b>create</b> is not implemented for model <b>%s</b>',
$_GET['model']) );
Yii::app()->end();
}
// Try to assign POST values to attributes
foreach($_POST as $var=>$value) {
// Does the model have this attribute? If not raise an error
if($model->hasAttribute($var))
$model->$var = $value;
else
$this->_sendResponse(500,
sprintf('Parameter <b>%s</b> is not allowed for model <b>%s</b>', $var,
$_GET['model']) );
}
// Try to save the model
if($model->save())
$this->_sendResponse(200, CJSON::encode($model));
else {
// Errors occurred
$msg = "<h1>Error</h1>";
$msg .= sprintf("Couldn't create model <b>%s</b>", $_GET['model']);
$msg .= "<ul>";
foreach($model->errors as $attribute=>$attr_errors) {
$msg .= "<li>Attribute: $attribute</li>";
$msg .= "<ul>";
foreach($attr_errors as $attr_error)
$msg .= "<li>$attr_error</li>";
$msg .= "</ul>";
}
$msg .= "</ul>";
$this->_sendResponse(500, $msg );
}
}
Fixed by removing $POST loop and changing to a JSON object scheme only. Here is the code if anyone happens to find themselves in this situation.
//read the post input (use this technique if you have no post variable name):
$post = file_get_contents("php://input");
//decode json post input as php array:
$data = CJSON::decode($post, true);
//load json data into model:
$model->attributes = $data;
Used that instead of the foreach loop through $_POST variables. Now it accepts a json object instead. Happy Coding all!
Honestly, I'm not sure what your problem is. If you can see the values POSTed in $_POST but not assigned to $model, it's probably because you did not specify the validation rules for those fields in the model. If you do not need to validate the fields, simply mark them as 'safe' in the model's rules() function like below.
public function rules() {
return array(
array('fieldName1,fieldName2', 'safe'),
);
}

Cakephp - If Request is JSON?

I have read the RequestHandler part in cookbook. There are isXml(), isRss(), etc. But there's no isJson().
Any other way to check whether a request is JSON?
So when the url is mysite.com/products/view/1.json it will give JSON data, but without .json it will give the HTML View.
Thanks
I dont think cakePHP has some function like isJson() for json data, you could create your custom though, like:
//may be in your app controller
function isJson($data) {
return (json_decode($data) != NULL) ? true : false;
}
//and you can use it in your controller
if( $this->isJson($your_request_data) ) {
...
}
Added:
if you want to check .json extension and process accordingly, then you could do in your controller:
$this->request->params['ext']; //which would give you 'json' if you have .json extension
CakePHP is handling this correctly, because JSON is a response type and not a type of request. The terms request and response might be causing some confusing. The request object represents the header information of the HTTP request sent to the server. A browser usually sends POST or GET requests to a server, and those requests can not be formatted as JSON. So it's not possible for a request to be of type JSON.
With that said, the server can give a response of JSON and a browser can put in the request header that it supports a JSON response. So rather than check what the request was. Check what accepted responses are supported by the browser.
So instead of writing $this->request->isJson() you should write $this->request->accepts('application/json').
This information is ambiguously shown in the document here, but there is no reference see also links in the is(..) documentation. So many people look there first. Don't see JSON and assume something is missing.
If you want to use a request detector to check if the browser supports a JSON response, then you can easily add a one liner in your beforeFilter.
$this->request->addDetector('json',array('callback'=>function($req){return $req->accepts('application/json');}));
There is a risk associated with this approach, because a browser can send multiple response types as a possible response from the server. Including a wildcard for all types. So this limits you to only requests that indicate a JSON response is supported. Since JSON is a text format a type of text/plain is a valid response type for a browser expecting JSON.
We could modify our rule to include text/plain for JSON responses like this.
$this->request->addDetector('json',array('callback'=>function($req){
return $req->accepts('application/json') || $req->accepts('text/plain');
}));
That would include text/plain requests as a JSON response type, but now we have a problem. Just because the browser supports a text/plain response doesn't mean it's expecting a JSON response.
This is why it's better to incorporate a naming convention into your URL to indicate a JSON response. You can use a .json file extension or a /json/controller/action prefix.
I prefer to use a named prefix for URLs. That allows you to create json_action methods in your controller. You can then create a detector for the prefix like this.
$this->request->addDetector('json',array('callback'=>function($req){return isset($req->params['prefix']) && $req->params['prefix'] == 'json';}));
Now that detector will always work correctly, but I argue it's an incorrect usage of detecting a JSON request. Since there is no such thing as a JSON request. Only JSON responses.
You can make your own detectors. See: http://book.cakephp.org/2.0/en/controllers/request-response.html#inspecting-the-request
For example in your AppController.php
public function beforeFilter() {
$this->request->addDetector(
'json',
[
'callback' => [$this, 'isJson']
]
);
parent::beforeFilter();
}
public function isJson() {
return $this->response->type() === 'application/json';
}
Now you can use it:
$this->request->is('json'); // or
$this->request->isJson();
Have you looked through and followed the very detailed instructions in the book?:
http://book.cakephp.org/2.0/en/views/json-and-xml-views.html
class TestController extends Controller {
public $autoRender = false;
public function beforeFilter() {
$this->request->addDetector('json', array('env' => 'CONTENT_TYPE', 'pattern' => '/application\/json/i'));
parent::beforeFilter();
}
public function index() {
App::uses('HttpSocket', 'Network/Http');
$url = 'http://localhost/myapp/test/json';
$json = json_encode(
array('foo' => 'bar'),
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
);
$options = array('header' => array('Content-Type' => 'application/json'));
$request = new HttpSocket();
$body = $request->post($url, $json, $options)->body;
$this->response->body($body);
}
public function json() {
if ($this->request->isJson()) {
$data = $this->request->input('json_decode');
$value = property_exists($data, 'foo') ? $data->foo : '';
}
$body = (isset($value) && $value === 'bar') ? 'ok' : 'fail';
$this->response->body($body);
}
}
Thanks a lot Mr #Schlaefer. I read your comment and try, Wow it's working now.
//AppController.php
function beforeFilter() {
$this->request->addDetector(
'json', [
'callback' => [$this, 'isJson']
]
);
parent::beforeFilter();
...
}
public function isJson() {
return $this->response->type() === 'application/json';
}
//TasksController.php
public $components = array('Paginator', 'Flash', Session','RequestHandler');
//Get tasks function return all tasks in json format
public function getTasks() {
$limit = 20;
$conditions = array();
if (!empty($this->request->query['status'])) {
$conditions = ['Task.status' => $this->request->query['status']];
}
if (!empty($this->request->query['limit'])) {
$limit = $this->request->query['limit'];
}
$this->Paginator->settings = array('limit' => $limit, 'conditions' => $conditions);
$tasks = $this->paginate();
if ($this->request->isJson()) {
$this->set(
array(
'tasks' => $tasks,
'_serialize' => array('tasks')
));
}
}
In case anybody is reading this in the days of CakePHP 4, the correct and easy way to do this is by using $this->request->is('json').

Return JSON based on "Accept: application/json" from a Symfony2 controller

I've got a CRUD form generated via the SensioGeneratorBundle, as described here. This works great.
However, I would like to also return JSON, rather than HTML responses, if the "Accept" HTTP header contains only "application/json". I'm working on a prototype for a JSON service and this would help me jump start things.
I figured out that I can turn my entities into a JSON string like this:
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
$serializer = new Serializer(array(new GetSetMethodNormalizer()), array('json' => new
JsonEncoder()));
$json = $serializer->serialize($entity, 'json');
However, at the end of that, $json contains a string that is my JSON data. I want to just directly output that to the requestor, rather than render the usual view. I've tried returning a new JsonResponse($json), but it re-encodes the JSON string, so it winds up double-encoded.
So I have two questions:
What is the "correct" way to inspect the HTTP requestion headers? I know I can just look in $_SERVER, but I'm thinking that there may be a better way to do this in Symfony2.
What is the "correct" way to return a JSON string, or to translate my entities into JSON that is returned straight to the requestor, without rendering the usual view.
Thanks!
This will verify if the current request is XHR and then send back properly formatted JSON data:
public function someAction(Request $request)
{
if ($request->isXmlHttpRequest()) {
$serializer = new Serializer(array(
new GetSetMethodNormalizer()
), array(
'json' => new JsonEncoder()
));
$response = $serializer->serialize(array(
'success' => true,
'data' => array(
'entity' => $entities,
)
), 'json');
return new Response($response, 200, array('Content-Type' => 'application/json'));
} else {
// Run "normal" request code, render a view
}
}
By the way, JMSSerializerBundle makes the serializing syntax more straightforward (because, let's face it, the native Symfony way for this is ugly) and also provides some additionnal features such as excluding entity fields to serialize (through annotations).
With JMS, my code looks like this:
if ($request->isXmlHttpRequest()) {
$response = array('success' => true, 'data' => array(
'entity' => $this->container->get('serializer')->serialize($entity, 'json'),
'lastPage' => $lastPage,
));
return new Response(json_encode($response), 200, array('Content-Type' => 'application/json'));
}
And finally, 'success' and 'data' are in no way required, it's just the structure I use to split status and data to be readable in JavaScript.

Silex & phpunit JSON response

I am trying to write some tests for Silex using phpunit.
I have a class Symfony\Component\BrowserKit\Client that generates a Crawler object.
This object expects the results of the client to be xhtml however my api that I am trying to test returns JSON and the crawler does not allow this.
Is there a built in class in either Silex or phpunit that will work with JSON or will I have to roll my own?
Cheers
There is nothing special for dealing with json, but you can use the client without using the crawler. Simply call getResponse() on the client to get the response, like this:
$client = $this->createClient();
$client->request('GET', '/');
$response = $client->getResponse();
$data = json_decode($response->getContent(), true);
$this->assertSame(array('id' => 1, 'name' => 'igorw'), $data['users'][0]);
I suggest you move this logic into a helper method on the test case and use that.
For sending an JSON request in Symfony 2 Browser-Kit use HTTP_ACCEPT instead of ACCEPT and HTTP_CONTENT_TYPE instead of CONTENT_TYPE. Here is an example for sending POST request with some $data:
$client->request(
$method = 'POST',
$uri,
$parameters = array(),
$files = array(),
$server = array(
'HTTP_CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=UTF-8', // for sending urlencoded data
//or 'HTTP_CONTENT_TYPE' => 'application/json', // for sending JSON data
'HTTP_ACCEPT' => 'application/json',
),
$content = $data,
$changeHistory = true
);
$response = $client->getResponse();
$response_data = json_decode($response->getContent(), true);