123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428 |
- <?php
- /**
- * Request Client. Processes a [Request] and handles [HTTP_Caching] if
- * available. Will usually return a [Response] object as a result of the
- * request unless an unexpected error occurs.
- *
- * @package Kohana
- * @category Base
- * @author Kohana Team
- * @copyright (c) Kohana Team
- * @license https://koseven.ga/LICENSE.md
- * @since 3.1.0
- */
- abstract class Kohana_Request_Client {
- /**
- * @var Cache Caching library for request caching
- */
- protected $_cache;
- /**
- * @var bool Should redirects be followed?
- */
- protected $_follow = FALSE;
- /**
- * @var array Headers to preserve when following a redirect
- */
- protected $_follow_headers = ['authorization'];
- /**
- * @var bool Follow 302 redirect with original request method?
- */
- protected $_strict_redirect = TRUE;
- /**
- * @var array Callbacks to use when response contains given headers
- */
- protected $_header_callbacks = [
- 'Location' => 'Request_Client::on_header_location'
- ];
- /**
- * @var int Maximum number of requests that header callbacks can trigger before the request is aborted
- */
- protected $_max_callback_depth = 5;
- /**
- * @var int Tracks the callback depth of the currently executing request
- */
- protected $_callback_depth = 1;
- /**
- * @var array Arbitrary parameters that are shared with header callbacks through their Request_Client object
- */
- protected $_callback_params = [];
- /**
- * Creates a new `Request_Client` object,
- * allows for dependency injection.
- *
- * @param array $params Params
- */
- public function __construct(array $params = [])
- {
- foreach ($params as $key => $value)
- {
- if (method_exists($this, $key))
- {
- $this->$key($value);
- }
- }
- }
- /**
- * Processes the request, executing the controller action that handles this
- * request, determined by the [Route].
- *
- * 1. Before the controller action is called, the [Controller::before] method
- * will be called.
- * 2. Next the controller action will be called.
- * 3. After the controller action is called, the [Controller::after] method
- * will be called.
- *
- * By default, the output from the controller is captured and returned, and
- * no headers are sent.
- *
- * $request->execute();
- *
- * @param Request $request
- * @param Response $response
- * @return Response
- * @throws Kohana_Exception
- * @uses [Kohana::$profiling]
- * @uses [Profiler]
- */
- public function execute(Request $request)
- {
- // Prevent too much recursion of header callback requests
- if ($this->callback_depth() > $this->max_callback_depth())
- throw new Request_Client_Recursion_Exception(
- "Could not execute request to :uri - too many recursions after :depth requests",
- [
- ':uri' => $request->uri(),
- ':depth' => $this->callback_depth() - 1,
- ]);
- // Execute the request and pass the currently used protocol
- $orig_response = $response = Response::factory(['_protocol' => $request->protocol()]);
- if (($cache = $this->cache()) instanceof HTTP_Cache)
- return $cache->execute($this, $request, $response);
- $response = $this->execute_request($request, $response);
- // Execute response callbacks
- foreach ($this->header_callbacks() as $header => $callback)
- {
- if ($response->headers($header))
- {
- $cb_result = call_user_func($callback, $request, $response, $this);
- if ($cb_result instanceof Request)
- {
- // If the callback returns a request, automatically assign client params
- $this->assign_client_properties($cb_result->client());
- $cb_result->client()->callback_depth($this->callback_depth() + 1);
- // Execute the request
- $response = $cb_result->execute();
- }
- elseif ($cb_result instanceof Response)
- {
- // Assign the returned response
- $response = $cb_result;
- }
- // If the callback has created a new response, do not process any further
- if ($response !== $orig_response)
- break;
- }
- }
- return $response;
- }
- /**
- * Processes the request passed to it and returns the response from
- * the URI resource identified.
- *
- * This method must be implemented by all clients.
- *
- * @param Request $request request to execute by client
- * @param Response $response
- * @return Response
- * @since 3.2.0
- */
- abstract public function execute_request(Request $request, Response $response);
- /**
- * Getter and setter for the internal caching engine,
- * used to cache responses if available and valid.
- *
- * @param HTTP_Cache $cache engine to use for caching
- * @return HTTP_Cache
- * @return Request_Client
- */
- public function cache(HTTP_Cache $cache = NULL)
- {
- if ($cache === NULL)
- return $this->_cache;
- $this->_cache = $cache;
- return $this;
- }
- /**
- * Getter and setter for the follow redirects
- * setting.
- *
- * @param bool $follow Boolean indicating if redirects should be followed
- * @return bool
- * @return Request_Client
- */
- public function follow($follow = NULL)
- {
- if ($follow === NULL)
- return $this->_follow;
- $this->_follow = $follow;
- return $this;
- }
- /**
- * Getter and setter for the follow redirects
- * headers array.
- *
- * @param array $follow_headers Array of headers to be re-used when following a Location header
- * @return array
- * @return Request_Client
- */
- public function follow_headers($follow_headers = NULL)
- {
- if ($follow_headers === NULL)
- return $this->_follow_headers;
- $this->_follow_headers = array_map('strtolower', $follow_headers);
- return $this;
- }
- /**
- * Getter and setter for the strict redirects setting
- *
- * [!!] HTTP/1.1 specifies that a 302 redirect should be followed using the
- * original request method. However, the vast majority of clients and servers
- * get this wrong, with 302 widely used for 'POST - 302 redirect - GET' patterns.
- * By default, Kohana's client is fully compliant with the HTTP spec. Some
- * non-compliant third party sites may require that strict_redirect is set
- * FALSE to force the client to switch to GET following a 302 response.
- *
- * @param bool $strict_redirect Boolean indicating if 302 redirects should be followed with the original method
- * @return Request_Client
- */
- public function strict_redirect($strict_redirect = NULL)
- {
- if ($strict_redirect === NULL)
- return $this->_strict_redirect;
- $this->_strict_redirect = $strict_redirect;
- return $this;
- }
- /**
- * Getter and setter for the header callbacks array.
- *
- * Accepts an array with HTTP response headers as keys and a PHP callback
- * function as values. These callbacks will be triggered if a response contains
- * the given header and can either issue a subsequent request or manipulate
- * the response as required.
- *
- * By default, the [Request_Client::on_header_location] callback is assigned
- * to the Location header to support automatic redirect following.
- *
- * $client->header_callbacks(array(
- * 'Location' => 'Request_Client::on_header_location',
- * 'WWW-Authenticate' => function($request, $response, $client) {return $new_response;},
- * );
- *
- * @param array $header_callbacks Array of callbacks to trigger on presence of given headers
- * @return Request_Client
- */
- public function header_callbacks($header_callbacks = NULL)
- {
- if ($header_callbacks === NULL)
- return $this->_header_callbacks;
- $this->_header_callbacks = $header_callbacks;
- return $this;
- }
- /**
- * Getter and setter for the maximum callback depth property.
- *
- * This protects the main execution from recursive callback execution (eg
- * following infinite redirects, conflicts between callbacks causing loops
- * etc). Requests will only be allowed to nest to the level set by this
- * param before execution is aborted with a Request_Client_Recursion_Exception.
- *
- * @param int $depth Maximum number of callback requests to execute before aborting
- * @return Request_Client|int
- */
- public function max_callback_depth($depth = NULL)
- {
- if ($depth === NULL)
- return $this->_max_callback_depth;
- $this->_max_callback_depth = $depth;
- return $this;
- }
- /**
- * Getter/Setter for the callback depth property, which is used to track
- * how many recursions have been executed within the current request execution.
- *
- * @param int $depth Current recursion depth
- * @return Request_Client|int
- */
- public function callback_depth($depth = NULL)
- {
- if ($depth === NULL)
- return $this->_callback_depth;
- $this->_callback_depth = $depth;
- return $this;
- }
- /**
- * Getter/Setter for the callback_params array, which allows additional
- * application-specific parameters to be shared with callbacks.
- *
- * As with other Kohana setter/getters, usage is:
- *
- * // Set full array
- * $client->callback_params(array('foo'=>'bar'));
- *
- * // Set single key
- * $client->callback_params('foo','bar');
- *
- * // Get full array
- * $params = $client->callback_params();
- *
- * // Get single key
- * $foo = $client->callback_params('foo');
- *
- * @param string|array $param
- * @param mixed $value
- * @return Request_Client|mixed
- */
- public function callback_params($param = NULL, $value = NULL)
- {
- // Getter for full array
- if ($param === NULL)
- return $this->_callback_params;
- // Setter for full array
- if (is_array($param))
- {
- $this->_callback_params = $param;
- return $this;
- }
- // Getter for single value
- elseif ($value === NULL)
- {
- return Arr::get($this->_callback_params, $param);
- }
- // Setter for single value
- else
- {
- $this->_callback_params[$param] = $value;
- return $this;
- }
- }
- /**
- * Assigns the properties of the current Request_Client to another
- * Request_Client instance - used when setting up a subsequent request.
- *
- * @param Request_Client $client
- */
- public function assign_client_properties(Request_Client $client)
- {
- $client->cache($this->cache());
- $client->follow($this->follow());
- $client->follow_headers($this->follow_headers());
- $client->header_callbacks($this->header_callbacks());
- $client->max_callback_depth($this->max_callback_depth());
- $client->callback_params($this->callback_params());
- }
- /**
- * The default handler for following redirects, triggered by the presence of
- * a Location header in the response.
- *
- * The client's follow property must be set TRUE and the HTTP response status
- * one of 201, 301, 302, 303 or 307 for the redirect to be followed.
- *
- * @param Request $request
- * @param Response $response
- * @param Request_Client $client
- */
- public static function on_header_location(Request $request, Response $response, Request_Client $client)
- {
- // Do we need to follow a Location header ?
- if ($client->follow() AND in_array($response->status(), [201, 301, 302, 303, 307]))
- {
- // Figure out which method to use for the follow request
- switch ($response->status())
- {
- default:
- case 301:
- case 307:
- $follow_method = $request->method();
- break;
- case 201:
- case 303:
- $follow_method = Request::GET;
- break;
- case 302:
- // Cater for sites with broken HTTP redirect implementations
- if ($client->strict_redirect())
- {
- $follow_method = $request->method();
- }
- else
- {
- $follow_method = Request::GET;
- }
- break;
- }
- // Prepare the additional request, copying any follow_headers that were present on the original request
- $orig_headers = $request->headers()->getArrayCopy();
- $follow_header_keys = array_intersect(array_keys($orig_headers), $client->follow_headers());
- $follow_headers = \Arr::extract($orig_headers, $follow_header_keys);
- $follow_request = Request::factory($response->headers('Location'))
- ->method($follow_method)
- ->headers($follow_headers);
- if ($follow_method !== Request::GET)
- {
- $follow_request->body($request->body());
- }
- return $follow_request;
- }
- return NULL;
- }
- }
|