How can I set a cookie with a json response?
I noticed, for me at least, the following command is the only thing working that sets a cookie:
return Redirect::to('/')
->withCookie(Cookie::make('blog', $cookie_values, 1000));
Of course if it was an ajax request it would return the target of the redirect.
How could I translate this to an ajax request and return a json response with the cookie?
I was able to set a cookie with a json response with the following code:
$cookie_values = array(
'name' => Input::get('name'),
'id' => Auth::user()->id,
'login_success' => 1);
if(Request::ajax())
{
$cookie = Cookie::make('blog', $cookie_values, 1000);
$response = Response::json($cookie_values);
$response->headers->setCookie($cookie);
return $response;
}
Great hint!
Having a look at Symfony\Component\HttpFoundation\ResponseHeaderBag also revealed how to set headers for a json response when having problems with HTTP access control:
$response->headers->set('Access-Control-Allow-Origin', '/* your subdomain */');
Related
I'm building a small application to store contacts in the database, I've finished the GET/POST routes, and worked fine, now I'm on the API routes (in order to use AJAX calls). I can store the information if all fields are present in the POST request, nonetheless, If I want to send messages back to the call (to send feedback about why the contact hasn't been stored) the response is sending me to the main route www.myapp.com (with no messages) and I want to send a json back with the "reason".
At this moment I only validate if the 'nombre', 'correo', 'telefono' have information with standard Laravel's request validate method.
This is my LeadController
public function storeApi(Request $request)
{
$request -> validate([
'nombre' => 'required',
'correo' => 'required' ,
'telefono' => 'required'
]);
if(Lead::create($request->all())){
$result[] = ['saved' => true];
}else{
$result[] = ['saved' => false,
'reason' => 'Some data is missing'];
return response()-> json($result);
};
return response()-> json($result);
}
When the record is stored, it does send back the Json {'saved' : true} but when fails It just sends you back to the '/' Route: www.myapp.com
How can I send the messages back to the POST call?
It is redirecting back to "/" because $request->validate() method throws \Illuminate\Validation\ValidationException exception..
There are try ways to handle this request.
Put try catch block around your validate code
Or Handle this expection in app\Exception\Handler.php, and return the response in JSON format.
After some further reading I just change the way the information is validated using the Validator Class:
public function storeApi(Request $request)
{
$validator = \Validator::make($request->all(), ['nombre' => 'required', 'correo' => 'required', 'telefono' => 'required']);
if($validator->fails()){
return response()->json($validator->errors(), 422);
}else {
//ready to store
}
}
This way I don't let the ValidationException exception occurs before sending the feedback to the call.
I'm using HTTP::UserAgent to try and use GitHub API from a program. Here's the program
use HTTP::UserAgent;
my $greeting = (%*ENV<BODY> ~~ /[Mm]erry/)??%*ENV<GREETING>!!%*ENV<HEY>;
my $url = "https://api.github.com/repos/JJ/raku-advent-calendar-article-2019/issues/%*ENV<ISSUE>/comments";
my %headers = Authorization => "token %*ENV<TOKEN>" ;
my %payload = body => $greeting;
my $agent = HTTP::UserAgent.new( useragent => "JJ's Xmas commenter" );
say $agent.post( $url, %payload,
Authorization => "token %*ENV<TOKEN>",
Content-Type => "application/json" );
If the content-type is not established, there's a malformed JSON error. If it's used, however, the error is different: 422 Unprocessable Entity. When using curl or similar, you can usually post directly the JSON string, but post in this case does not admit single strings, or if it's a form, I have no idea what to use as key. Can you please help?
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').
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);
I have been playing around with using rest-client to access a rails app I have written. I've written a quick script to log in and make a post request. Everything is working but I did have to work round the fact that no authenticity_token is served if you make a request for a form in json. I had to make a regular html request in other get the authenticity_token and then included this in the json I submitted as part of my post request. Basically I have a quick an dirty script like the one below
private_resource = RestClient::Resource.new( 'https://mysite.com')
params = {:user => {:email => 'user#mysite.com', :password => 'please'}}
#log in
login_response = private_resource['users/sign_in'].post(params, :content_type => :json, :accept => :json)
#get cookie
cookie = login_response.cookies
#get json
json_response = private_resource['products/new'].get(:content_type => :json, :accept => :json, :cookies => cookie)
#another request that returns html form with authenticity token
response_with_token = private_resource['products/new'].get( :cookies => cookie)
#extract token
token = Nokogiri::XML(response_with_token).css('input[name=authenticity_token]').first.attr('value')
#update cookie
cookie = response_with_token.cookies
#populate form and insert token
form = JSON.parse(json_response)
form['name'] = "my product"
form['authenticity_token'] = token
#submit the request
private_resource['products'].post(form.to_json, {:cookies => cookie, :content_type => :json, :accept => :json})
There is the option to turn off CSRF protection for json requests but I would rather not do that. I could go the mechanize route or something similar and then I wouldn't worry about json requests with CSRF but I just wanted to play around with doing this stuff with rest-client
I guess I'm just curious to know if there is a reason why no authenticity_token is served for json requests and I'm also wondering if there is a better way of solving the token problem than the pretty hacky approach I've taken here
Put the below code into your application controller :
def verified_request?
if request.content_type == "application/json"
true
else
super()
end
end
And call this method using before_filter .
For more details check :
http://blog.technopathllc.com/2011/09/rails-31-csrf-token-authenticity-for.html
And check this issue in rails : https://github.com/rails/rails/issues/3041
In your app/views/products/new.json.jbuilder, add this:
json.authenticity_token form_authenticity_token
This will insert a key "authenticity_token" with value being the token, so in your json_response you get the token as well. Idea from this answer.