How is URL delivered to the router in Cake? - cakephp

www.foo.com/blog/posts/view/12/12/2013
There is no possible file corresponds to this request. So this URL needs to be parsed by some logic, or else you get a 404.
Since there is no corresponding file, the response cannot be automated by the server. I was just wondering which part of Cake FIRST responds to a request like this. I understand simple page requests are first parsed and resolved by the router. But the URL cannot just magically get to the front door of the router, right? I really want to know what is going on behind the scene that brings the URL to the router.

Check your app/webroot/index.php, the bottom:
$Dispatcher = new Dispatcher();
$Dispatcher->dispatch(
new CakeRequest(),
new CakeResponse()
);
The key method of the Dispatcher is parseParams. This method gets triggered through the event system at the start of Dispatcher::dispatch(), checkt that method as well in the class.
What happens is basically that the dispatcher uses the router to parse the plain url and turns it into params and adds the parsed result to the request object and then dispatches a controller based on the parsed result.
/**
* Applies Routing and additionalParameters to the request to be dispatched.
* If Routes have not been loaded they will be loaded, and app/Config/routes.php will be run.
*
* #param CakeEvent $event containing the request, response and additional params
* #return void
*/
public function parseParams($event) {
$request = $event->data['request'];
Router::setRequestInfo($request);
$params = Router::parse($request->url);
$request->addParams($params);
if (!empty($event->data['additionalParams'])) {
$request->addParams($event->data['additionalParams']);
}
}

Related

Spring React and Sessions.. how to keep session

I have set up my spring to maintain a HTTP session on an object like so:
#Component
#SessionScope
public class Basket { .. }
controller:
#PostMapping(path="/basket/addItem/{user}", consumes = "application/json", produces = "application/json")
public Basket createBasket(#PathVariable String user, #RequestBody Item item) {
System.out.println("POSTING..................................");
return basketService.addItem(user, item);
}
now when i use a REST client, in firefox i can see that the session bean is created and maintained for the duration - multiple calls. I can append to the object. If i try another client, it gets its own session with its own bean. great..
spring logs the following:
Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [269] milliseconds.
However im trying to create a basic front end in react, when react makes a request using axios it gets a new bean every time, which means that the session must be ending after each call. IS that correct? or im not tying it to the react application...
Maybe the approach im taking is not correct, maybe i should use a a different approach, Im trying to learn about spring boot, so its a basic project... and right now i want to maintain user session for a cart. so subsequent calls i can append to the object...
by adding the following to my controller it all began to work.
#CrossOrigin(origins = { "http://localhost:3000" }, allowedHeaders = "*", allowCredentials = "true")

How to use a CookieCollection in multiple functions

I'm setting up a web page using cookies to determine if the user already logged in, using a cookie containing his id. Problem is : The cookie is either not written or the cookie collection is not updated.
I've tried reading the documentation, but it does not define the usage of CookieCollection.
Here's the function where i write my cookie :
function displayData(){
$id = $this->getRequest()->getSession()->read('id');
$cookies = CookieCollection::createFromServerRequest($this->getRequest());
if(!$cookies->has('id')){
$cookie = (new Cookie('id'))
->withValue($id)
->withExpiry(new DateTime('+999 year'))
->withPath('/')
->withDomain('break-first.eu')
->withSecure(true)
->withHttpOnly(true);
$cookies = $cookies->add($cookie);
}
// Other stuff
}
And where I try reading it :
function index(){
$cookies = $this->getRequest()->getCookieCollection();
dd($cookies);
}
I expect having a cookie named "id", but I don't have it. Only CAKEPHP and pll_language are showing up.
First things first, CakePHP provides authentication functionality with cookie authentication, you may want to have a look at that instead of driving a custom solution.
Cookbook > Plugins > Authentication
That being said, what you're doing there will create a cookie collection object, which however is just that, a lone object somewhere in space, it won't affect the state of your application, in order for that to happen you have to actually modify the response object.
However what you're trying to do there doesn't require cookie collections in the first place, you can simply read and write cookies directly via the methods provided by the request and response objects, like:
// will be `null` in case the cookie doesn't exist
$cookie = $this->getRequest()->getCookie('id');
// responses are immutable, they need to be reassinged
this->setResponse(
$this->getResponse()->withCookie(
(new Cookie('id'))
->withValue($id)
->withExpiry(new DateTime('+999 year'))
->withPath('/')
->withDomain('break-first.eu')
->withSecure(true)
->withHttpOnly(true)
)
);
And if you where to use a cookie collection for whatever reason, then you'd use withCookieCollection() to pass it into the response:
$this->setResponse($this->getResponse()->withCookieCollection($cookies));
If you run into strict typing errors, you could for example create a custom reponse class with an overridden Response::convertCookieToArray() method and cast the string to an integer there (make sure that PHP_INT_MAX covers your target date timestamp, 32-Bit incompatibility is why the fix that landed in CakePHP 4.x, probably won't come to 3.x), something like:
src/Http/Response.php
namespace App\Http;
use Cake\Http\Cookie\CookieInterface;
use Cake\Http\Response as CakeResponse;
class Response extends CakeResponse
{
protected function convertCookieToArray(CookieInterface $cookie)
{
$data = parent::convertCookieToArray($cookie);
$data['expire'] = (int)$data['expire'];
return $data;
}
}
You can pass that into the app in your webroot/index.php file, as the second argument of the $server->run() call:
// ...
$server->emit($server->run(null, new \App\Http\Response()));
See also
Cookbook > Request & Response Objects > Request > Cookies
Cookbook > Request & Response Objects > Response > Setting Cookies

Setting a header in CakePHP (MVC)

I'm trying to integrate PayPal's IPN code into CakePHP 3.
namespace App\Controller;
use PayPal\Api\PaypalIPN;
class IpnController extends AppController
{
public function index()
{
$this->autoRender = false;
$ipn = new PayPalIPN();
// Use the sandbox endpoint during testing.
$ipn->useSandbox();
$verified = $ipn->verifyIPN();
if ($verified) {
/*
* Process IPN
* A list of variables is available here:
* https://developer.paypal.com/webapps/developer/docs/classic/ipn/integration-guide/IPNandPDTVariables/
*/
}
// Reply with an empty 200 response to indicate to paypal the IPN was received correctly.
header("HTTP/1.1 200 OK");
}
}
This is failing to validate on PayPal's end and I'm suspecting it has to do with setting the headers in the controller view.
Is there a way to set the header properly in CakePHP's controller.
I had this code running stand alone (in just a php file) and it seemed to work just fine.
You should not output any data in your controller action - that means you should not use echo, header() or any function or construct that would return anything to browser. If you do, you will encounter a "headers already sent" error.
If you want to set headers, you should use withHeader() or withAddedHeader() methods of Cake\Http\Response.
For status codes, you also have withStatus() method:
$response = $this->response;
$response = $response->withStatus(200,"OK");
return $response; // returning response will stop controller from rendering a view.
More about setting headers can be found in docs:
Setting response headers in CakePHP 3
Cake\Http\Response::withStatus()
Maybe that's not very Cakish, but actually one can send headers this way - it just have to be followed by die; or exit; to prevent app from further response processing.
Anyway, for sure your problem is not associated with headers. IPN seems to doesn't work properly with Paypal Sandbox. Maybe you should try it other way with ApiContext class?

What's the proper way to serve JSONP with CakePHP?

I want to serve JSONP content with CakePHP and was wondering what's the proper way of doing it so.
Currently I'm able to serve JSON content automatically by following this CakePHP guide.
Ok, I found a solution on this site. Basically you override the afterFilter method with:
public function afterFilter() {
parent::afterFilter();
if (empty($this->request->query['callback']) || $this->response->type() != 'application/json') {
return;
}
// jsonp response
App::uses('Sanitize', 'Utility');
$callbackFuncName = Sanitize::clean($this->request->query['callback']);
$out = $this->response->body();
$out = sprintf("%s(%s)", $callbackFuncName, $out);
$this->response->body($out);
}
I hope it helps someone else as well.
I've as yet not found a complete example of how to correctly return JSONP using CakePHP 2, so I'm going to write it down. OP asks for the correct way, but his answer doesn't use the native options available now in 2.4. For 2.4+, this is the correct method, straight from their documentation:
Set up your views to accept/use JSON (documentation):
Add Router::parseExtensions('json'); to your routes.php config file. This tells Cake to accept .json URI extensions
Add RequestHandler to the list of components in the controller you're going to be using
Cake gets smart here, and now offers you different views for normal requests and JSON/XML etc. requests, allowing you flexibility in how to return those results, if needed. You should now be able to access an action in your controller by:
using the URI /controller/action (which would use the view in /view/controller/action.ctp), OR
using the URI /controller/action.json (which would use the view in /view/controller/json/action.ctp)
If you don't want to define those views i.e. you don't need to do any further processing, and the response is ready to go, you can tell CakePHP to ignore the views and return the data immediately using _serialize. Using _serialize will tell Cake to format your response in the correct format (XML, JSON etc.), set the headers and return it as needed without you needing to do anything else (documentation). To take advantage of this magic:
Set the variables you want to return as you would a view variable i.e. $this->set('post', $post);
Tell Cake to serialize it into XML, JSON etc. by calling $this->set('_serialize', array('posts'));, where the parameter is the view variable you just set in the previous line
And that's it. All headers and responses will be taken over by Cake. This just leaves the JSONP to get working (documentation):
Tell Cake to consider the request a JSONP request by setting $this->set('_jsonp', true);, and Cake will go find the callback function name parameter, and format the response to work with that callback function name. Literally, setting that one parameter does all the work for you.
So, assuming you've set up Cake to accept .json requests, this is what your typical action could look like to work with JSONP:
public function getTheFirstPost()
$post = $this->Post->find('first');
$this->set(array(
'post' => $post, <-- Set the post in the view
'_serialize' => array('post'), <-- Tell cake to use that post
'_jsonp' => true <-- And wrap it in the callback function
)
);
And the JS:
$.ajax({
url: "/controller/get-the-first-post.json",
context: document.body,
dataType: 'jsonp'
}).done(function (data) {
console.log(data);
});
For CakePHP 2.4 and above, you can do this instead.
http://book.cakephp.org/2.0/en/views/json-and-xml-views.html#jsonp-response
So you can simply write:
$this->set('_jsonp', true);
in the relevant action.
Or you can simply write:
/**
*
* beforeRender method
*
* #return void
*/
public function beforeRender() {
parent::beforeRender();
$this->set('_jsonp', true);
}

How to know if a Kohana request is an internal one?

I'm writing an API using Kohana. Each external request must be signed by the client to be accepted.
However, I also sometime need to do internal requests by building a Request object and calling execute(). In these cases, the signature is unnecessary since I know the request is safe. So I need to know that the request was internal so that I can skip the signature check.
So is there any way to find out if the request was manually created using a Request object?
Can you use the is_initial() method of the request object? Using this method, you can determine if a request is a sub request.
Kohana 3.2 API, Request - is_initial()
It sounds like you could easily solve this issue by setting some sort of static variable your app can check. If it's not FALSE, then you know it's internal.
This is how I ended up doing it: I've overridden the Request object and added a is_server_side property to it. Now, when I create the request, I just set this to true so that I know it's been created server-side:
$request = Request::factory($url);
$request->is_server_side(true);
$response = $request->execute();
Then later in the controller receiving the request:
if ($this->request->is_server_side()) {
// Skip signature check
} else {
// Do signature check
}
And here is the overridden request class in application/classes/request.php:
<?php defined('SYSPATH') or die('No direct script access.');
class Request extends Kohana_Request {
protected $is_server_side_ = false;
public function is_server_side($v = null) {
if ($v === null) return $this->is_server_side_;
$this->is_server_side_ = $v;
}
}
Looking through Request it looks like your new request would be considered an internal request but does not have any special flags it sets to tell you this. Look at 782 to 832 in Kohana_Request...nothing to help you.
With that, I'd suggest extending the Kohana_Request_Internal to add a flag that shows it as internal and pulling that in your app when you need to check if it is internal/all others.
Maybe you are looking for is_external method:
http://kohanaframework.org/3.2/guide/api/Request#is_external
Kohana 3.3 in the controller :
$this->request->is_initial()
http://kohanaframework.org/3.3/guide-api/Request#is_initial

Resources