From f73e5f96d8368aab54bb9acb32208e09374a475a Mon Sep 17 00:00:00 2001 From: Jan Tvrdik Date: Sun, 5 Jun 2016 09:11:31 +0200 Subject: [PATCH] Request: add getBody() --- src/Http/IRequest.php | 6 ++++ src/Http/Request.php | 24 ++++++++++++-- src/Http/RequestFactory.php | 49 ++++++++++++++++++++++++++++- src/Http/exceptions.php | 18 +++++++++++ tests/Http/RequestFactory.body.phpt | 48 ++++++++++++++++++++++++++++ 5 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 src/Http/exceptions.php create mode 100644 tests/Http/RequestFactory.body.phpt diff --git a/src/Http/IRequest.php b/src/Http/IRequest.php index cfd0a3f3..9fb99ef6 100644 --- a/src/Http/IRequest.php +++ b/src/Http/IRequest.php @@ -135,4 +135,10 @@ function getRemoteHost(); */ function getRawBody(); + /** + * Returns parsed content of HTTP request body. + * @return mixed + * @throws InvalidRequestBodyException + */ + function getBody(); } diff --git a/src/Http/Request.php b/src/Http/Request.php index 5204e9b8..e47a41d4 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -26,6 +26,7 @@ * @property-read string|NULL $remoteAddress * @property-read string|NULL $remoteHost * @property-read string|NULL $rawBody + * @property-read string|NULL $body */ class Request implements IRequest { @@ -58,9 +59,14 @@ class Request implements IRequest /** @var callable|NULL */ private $rawBodyCallback; + /** @var callable|NULL */ + private $bodyCallback; + - public function __construct(UrlScript $url, $query = NULL, $post = NULL, $files = NULL, $cookies = NULL, - $headers = NULL, $method = NULL, $remoteAddress = NULL, $remoteHost = NULL, $rawBodyCallback = NULL) + public function __construct( + UrlScript $url, $query = NULL, $post = NULL, $files = NULL, $cookies = NULL, + $headers = NULL, $method = NULL, $remoteAddress = NULL, $remoteHost = NULL, + $rawBodyCallback = NULL, $bodyCallback = NULL) { $this->url = $url; if ($query !== NULL) { @@ -75,6 +81,7 @@ public function __construct(UrlScript $url, $query = NULL, $post = NULL, $files $this->remoteAddress = $remoteAddress; $this->remoteHost = $remoteHost; $this->rawBodyCallback = $rawBodyCallback; + $this->bodyCallback = $bodyCallback; } @@ -289,7 +296,18 @@ public function getRemoteHost() */ public function getRawBody() { - return $this->rawBodyCallback ? call_user_func($this->rawBodyCallback) : NULL; + return $this->rawBodyCallback ? call_user_func($this->rawBodyCallback, $this) : NULL; + } + + + /** + * Returns parsed content of HTTP request body. + * @return mixed + * @throws InvalidRequestBodyException + */ + public function getBody() + { + return $this->bodyCallback ? call_user_func($this->bodyCallback, $this) : NULL; } diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 993fa191..54c5df3f 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -8,6 +8,7 @@ namespace Nette\Http; use Nette; +use Nette\Utils\Json; use Nette\Utils\Strings; @@ -33,6 +34,25 @@ class RequestFactory /** @var array */ private $proxies = []; + /** @var callable[] of function(Request $request): mixed */ + private $bodyParsers = []; + + + public function __construct() + { + $this->addBodyParser('application/x-www-form-urlencoded', function (Request $request) { + return $request->getPost(); + }); + + $this->addBodyParser('application/json', function (Request $request) { + try { + return Json::decode($request->getRawBody()); + } catch (Nette\Utils\JsonException $e) { + throw new InvalidRequestBodyException('Body is not a valid JSON', 0, $e); + } + }); + } + /** * @param bool @@ -56,6 +76,18 @@ public function setProxy($proxy) } + /** + * @param string + * @param callable function(Request $request): mixed|NULL + * @return self + */ + public function addBodyParser($contentType, $callback) + { + $this->bodyParsers[$contentType] = $callback; + return $this; + } + + /** * Creates current HttpRequest object. * @return Request @@ -235,7 +267,22 @@ public function createHttpRequest() return file_get_contents('php://input'); }; - return new Request($url, NULL, $post, $files, $cookies, $headers, $method, $remoteAddr, $remoteHost, $rawBodyCallback); + // parsed body + $bodyCallback = function (Request $request) use (& $body) { + if ($body === NULL) { + $contentType = $request->getHeader('Content-Type'); + foreach ($this->bodyParsers as $parserContentType => $parser) { + if (stripos($contentType, $parserContentType) === 0) { + $body = $parser($request); + break; + } + } + } + + return $body; + }; + + return new Request($url, NULL, $post, $files, $cookies, $headers, $method, $remoteAddr, $remoteHost, $rawBodyCallback, $bodyCallback); } } diff --git a/src/Http/exceptions.php b/src/Http/exceptions.php new file mode 100644 index 00000000..025fc850 --- /dev/null +++ b/src/Http/exceptions.php @@ -0,0 +1,18 @@ +rawBodyCallback = function () use ($rawBody) { + return $rawBody; + }; +}; + + +test(function () use ($factory, $setRawBody) { + $_SERVER = [ + 'CONTENT_TYPE' => 'application/json', + ]; + + $request = $factory->createHttpRequest(); + $setRawBody->bindTo($request, $request)->__invoke('[1, 2.0, "3", true, false, null, {}]'); + Assert::same('[1, 2.0, "3", true, false, null, {}]', $request->getRawBody()); + Assert::equal([1, 2.0, '3', TRUE, FALSE, NULL, new stdClass], $request->body); +}); + + +test(function () use ($factory, $setRawBody) { + $_SERVER = [ + 'CONTENT_TYPE' => 'application/x-www-form-urlencoded', + ]; + + $_POST = [ + 'a' => 'b', + ]; + + $request = $factory->createHttpRequest(); + $setRawBody->bindTo($request, $request)->__invoke('a=c'); + Assert::same('a=c', $request->getRawBody()); + Assert::equal(['a' => 'b'], $request->body); +});