2013-08-17 13:16:48 +04:00
< ? php
2019-12-03 21:57:53 +03:00
2018-02-21 10:51:46 +03:00
declare ( strict_types = 1 );
2019-12-03 21:57:53 +03:00
2013-08-17 13:16:48 +04:00
/**
2016-07-21 18:07:57 +03:00
* @ copyright Copyright ( c ) 2016 , ownCloud , Inc .
*
2015-03-26 13:44:34 +03:00
* @ author Bernhard Posselt < dev @ bernhard - posselt . com >
2020-04-29 12:57:22 +03:00
* @ author Christoph Wurst < christoph @ winzerhof - wurst . at >
2020-12-16 16:54:15 +03:00
* @ author Joas Schilling < coding @ schilljs . com >
2019-12-03 21:57:53 +03:00
* @ author Julius Härtl < jus @ bitgrid . net >
2016-05-26 20:56:05 +03:00
* @ author Lukas Reschke < lukas @ statuscode . ch >
2015-03-26 13:44:34 +03:00
* @ author Morris Jobke < hey @ morrisjobke . de >
2017-11-06 17:56:42 +03:00
* @ author Roeland Jago Douma < roeland @ famdouma . nl >
2015-03-26 13:44:34 +03:00
* @ author Thomas Müller < thomas . mueller @ tmit . eu >
* @ author Thomas Tanghus < thomas @ tanghus . net >
2013-08-17 13:16:48 +04:00
*
2015-03-26 13:44:34 +03:00
* @ license AGPL - 3.0
2013-08-17 13:16:48 +04:00
*
2015-03-26 13:44:34 +03:00
* This code is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License , version 3 ,
* as published by the Free Software Foundation .
2013-08-17 13:16:48 +04:00
*
2015-03-26 13:44:34 +03:00
* This program is distributed in the hope that it will be useful ,
2013-08-17 13:16:48 +04:00
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
2015-03-26 13:44:34 +03:00
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
2013-08-17 13:16:48 +04:00
*
2015-03-26 13:44:34 +03:00
* You should have received a copy of the GNU Affero General Public License , version 3 ,
2019-12-03 21:57:53 +03:00
* along with this program . If not , see < http :// www . gnu . org / licenses />
2013-08-17 13:16:48 +04:00
*
*/
2015-02-26 13:37:37 +03:00
2013-08-17 13:16:48 +04:00
namespace OC\AppFramework\Http ;
2019-11-22 22:52:10 +03:00
use OC\AppFramework\Http ;
use OC\AppFramework\Middleware\MiddlewareDispatcher ;
use OC\AppFramework\Utility\ControllerMethodReflector ;
2021-01-03 17:28:31 +03:00
use OC\DB\ConnectionAdapter ;
2013-10-11 12:07:57 +04:00
use OCP\AppFramework\Controller ;
2014-10-28 18:34:04 +03:00
use OCP\AppFramework\Http\DataResponse ;
2019-11-22 22:52:10 +03:00
use OCP\AppFramework\Http\Response ;
2020-09-25 15:47:14 +03:00
use OCP\IConfig ;
2014-05-06 18:29:19 +04:00
use OCP\IRequest ;
2020-09-25 15:47:14 +03:00
use Psr\Log\LoggerInterface ;
2013-08-17 13:16:48 +04:00
/**
2013-08-20 19:20:36 +04:00
* Class to dispatch the request to the middleware dispatcher
2013-08-17 13:16:48 +04:00
*/
class Dispatcher {
2018-02-21 10:51:46 +03:00
/** @var MiddlewareDispatcher */
2013-08-17 13:16:48 +04:00
private $middlewareDispatcher ;
2018-02-21 10:51:46 +03:00
/** @var Http */
2013-08-17 13:16:48 +04:00
private $protocol ;
2018-02-21 10:51:46 +03:00
/** @var ControllerMethodReflector */
2014-05-06 18:29:19 +04:00
private $reflector ;
2018-02-21 10:51:46 +03:00
/** @var IRequest */
2014-05-06 18:29:19 +04:00
private $request ;
2013-08-17 13:16:48 +04:00
2020-09-25 15:47:14 +03:00
/** @var IConfig */
private $config ;
2021-01-03 17:28:31 +03:00
/** @var ConnectionAdapter */
2020-09-25 15:47:14 +03:00
private $connection ;
/** @var LoggerInterface */
private $logger ;
2013-08-17 13:16:48 +04:00
/**
* @ param Http $protocol the http protocol with contains all status headers
* @ param MiddlewareDispatcher $middlewareDispatcher the dispatcher which
* runs the middleware
2014-11-27 16:19:00 +03:00
* @ param ControllerMethodReflector $reflector the reflector that is used to inject
2014-05-06 18:29:19 +04:00
* the arguments for the controller
* @ param IRequest $request the incoming request
2020-09-25 15:47:14 +03:00
* @ param IConfig $config
2021-01-03 17:28:31 +03:00
* @ param ConnectionAdapter $connection
2020-09-25 15:47:14 +03:00
* @ param LoggerInterface $logger
2013-08-17 13:16:48 +04:00
*/
public function __construct ( Http $protocol ,
2014-06-11 02:57:00 +04:00
MiddlewareDispatcher $middlewareDispatcher ,
ControllerMethodReflector $reflector ,
2020-09-25 15:47:14 +03:00
IRequest $request ,
IConfig $config ,
2021-01-03 17:28:31 +03:00
ConnectionAdapter $connection ,
2020-09-25 15:47:14 +03:00
LoggerInterface $logger ) {
2013-08-17 13:16:48 +04:00
$this -> protocol = $protocol ;
$this -> middlewareDispatcher = $middlewareDispatcher ;
2014-05-06 18:29:19 +04:00
$this -> reflector = $reflector ;
$this -> request = $request ;
2020-09-25 15:47:14 +03:00
$this -> config = $config ;
$this -> connection = $connection ;
$this -> logger = $logger ;
2013-08-17 13:16:48 +04:00
}
/**
* Handles a request and calls the dispatcher on the controller
* @ param Controller $controller the controller which will be called
* @ param string $methodName the method name which will be called on
* the controller
* @ return array $array [ 0 ] contains a string with the http main header ,
* $array [ 1 ] contains headers in the form : $key => value , $array [ 2 ] contains
* the response output
2014-11-27 16:19:00 +03:00
* @ throws \Exception
2013-08-17 13:16:48 +04:00
*/
2018-02-21 10:51:46 +03:00
public function dispatch ( Controller $controller , string $methodName ) : array {
$out = [ null , [], null ];
2013-08-17 13:16:48 +04:00
try {
2014-06-11 02:54:25 +04:00
// prefill reflector with everything thats needed for the
2014-05-06 18:29:19 +04:00
// middlewares
$this -> reflector -> reflect ( $controller , $methodName );
2013-08-17 13:16:48 +04:00
$this -> middlewareDispatcher -> beforeController ( $controller ,
$methodName );
2020-09-25 15:47:14 +03:00
$databaseStatsBefore = [];
if ( $this -> config -> getSystemValueBool ( 'debug' , false )) {
2021-01-03 17:28:31 +03:00
$databaseStatsBefore = $this -> connection -> getInner () -> getStats ();
2020-09-25 15:47:14 +03:00
}
2014-05-06 18:29:19 +04:00
$response = $this -> executeController ( $controller , $methodName );
2013-08-17 13:16:48 +04:00
2020-09-25 15:47:14 +03:00
if ( ! empty ( $databaseStatsBefore )) {
2021-01-03 17:28:31 +03:00
$databaseStatsAfter = $this -> connection -> getInner () -> getStats ();
2020-09-25 15:47:14 +03:00
$numBuilt = $databaseStatsAfter [ 'built' ] - $databaseStatsBefore [ 'built' ];
$numExecuted = $databaseStatsAfter [ 'executed' ] - $databaseStatsBefore [ 'executed' ];
if ( $numBuilt > 50 ) {
$this -> logger -> debug ( 'Controller {class}::{method} created {count} QueryBuilder objects, please check if they are created inside a loop by accident.' , [
2021-01-11 14:57:03 +03:00
'class' => get_class ( $controller ),
2020-09-25 15:47:14 +03:00
'method' => $methodName ,
'count' => $numBuilt ,
]);
}
if ( $numExecuted > 100 ) {
$this -> logger -> warning ( 'Controller {class}::{method} executed {count} queries.' , [
2021-01-11 14:57:03 +03:00
'class' => get_class ( $controller ),
2020-09-25 15:47:14 +03:00
'method' => $methodName ,
'count' => $numExecuted ,
]);
}
}
2013-08-20 19:20:36 +04:00
// if an exception appears, the middleware checks if it can handle the
// exception and creates a response. If no response is created, it is
// assumed that theres no middleware who can handle it and the error is
// thrown again
2020-04-10 15:19:56 +03:00
} catch ( \Exception $exception ) {
2013-08-17 13:16:48 +04:00
$response = $this -> middlewareDispatcher -> afterException (
$controller , $methodName , $exception );
2020-04-10 15:19:56 +03:00
} catch ( \Throwable $throwable ) {
2019-08-29 18:18:39 +03:00
$exception = new \Exception ( $throwable -> getMessage (), $throwable -> getCode (), $throwable );
$response = $this -> middlewareDispatcher -> afterException (
$controller , $methodName , $exception );
2013-08-17 13:16:48 +04:00
}
$response = $this -> middlewareDispatcher -> afterController (
$controller , $methodName , $response );
// depending on the cache object the headers need to be changed
2020-05-11 17:18:01 +03:00
$out [ 0 ] = $this -> protocol -> getStatusHeader ( $response -> getStatus ());
2014-11-27 16:19:00 +03:00
$out [ 1 ] = array_merge ( $response -> getHeaders ());
$out [ 2 ] = $response -> getCookies ();
2015-01-22 22:26:46 +03:00
$out [ 3 ] = $this -> middlewareDispatcher -> beforeOutput (
$controller , $methodName , $response -> render ()
);
$out [ 4 ] = $response ;
2013-08-17 13:16:48 +04:00
return $out ;
}
2014-05-06 18:29:19 +04:00
/**
* Uses the reflected parameters , types and request parameters to execute
* the controller
2014-05-11 15:59:48 +04:00
* @ param Controller $controller the controller to be executed
* @ param string $methodName the method on the controller that should be executed
2014-05-06 18:29:19 +04:00
* @ return Response
*/
2018-02-21 10:51:46 +03:00
private function executeController ( Controller $controller , string $methodName ) : Response {
$arguments = [];
2014-05-06 18:29:19 +04:00
// valid types that will be casted
2018-02-21 10:51:46 +03:00
$types = [ 'int' , 'integer' , 'bool' , 'boolean' , 'float' ];
2014-05-06 18:29:19 +04:00
2020-04-10 15:19:56 +03:00
foreach ( $this -> reflector -> getParameters () as $param => $default ) {
2014-05-06 18:29:19 +04:00
// try to get the parameter from the request object and cast
// it to the type annotated in the @param annotation
2014-05-13 12:40:49 +04:00
$value = $this -> request -> getParam ( $param , $default );
2014-05-06 18:29:19 +04:00
$type = $this -> reflector -> getType ( $param );
2014-06-11 02:54:25 +04:00
// if this is submitted using GET or a POST form, 'false' should be
2014-05-06 18:29:19 +04:00
// converted to false
2020-04-10 15:19:56 +03:00
if (( $type === 'bool' || $type === 'boolean' ) &&
2014-06-11 02:54:25 +04:00
$value === 'false' &&
2014-05-06 18:29:19 +04:00
(
$this -> request -> method === 'GET' ||
2014-06-11 02:54:25 +04:00
strpos ( $this -> request -> getHeader ( 'Content-Type' ),
2014-05-06 21:09:03 +04:00
'application/x-www-form-urlencoded' ) !== false
2014-05-06 18:29:19 +04:00
)
) {
$value = false ;
2020-04-10 15:19:56 +03:00
} elseif ( $value !== null && \in_array ( $type , $types , true )) {
2014-05-06 18:29:19 +04:00
settype ( $value , $type );
}
2014-06-11 02:54:25 +04:00
2014-05-06 18:29:19 +04:00
$arguments [] = $value ;
}
2018-02-21 10:51:46 +03:00
$response = \call_user_func_array ([ $controller , $methodName ], $arguments );
2014-05-06 18:29:19 +04:00
2014-10-28 18:34:04 +03:00
// format response
2020-04-10 15:19:56 +03:00
if ( $response instanceof DataResponse || ! ( $response instanceof Response )) {
2014-06-11 02:54:25 +04:00
2014-05-06 18:29:19 +04:00
// get format from the url format or request format parameter
$format = $this -> request -> getParam ( 'format' );
2014-06-11 02:54:25 +04:00
2014-05-06 18:29:19 +04:00
// if none is given try the first Accept header
2020-04-10 15:19:56 +03:00
if ( $format === null ) {
2014-06-11 02:57:00 +04:00
$headers = $this -> request -> getHeader ( 'Accept' );
2016-07-20 22:30:39 +03:00
$format = $controller -> getResponderByHTTPHeader ( $headers , null );
2014-06-11 02:57:00 +04:00
}
2014-05-06 18:29:19 +04:00
2016-07-20 22:30:39 +03:00
if ( $format !== null ) {
$response = $controller -> buildResponse ( $response , $format );
} else {
$response = $controller -> buildResponse ( $response );
}
2014-05-06 18:29:19 +04:00
}
return $response ;
}
2013-08-17 13:16:48 +04:00
}