Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request::getBody(), see #57 #58

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/Http/IRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,10 @@ function getRemoteHost();
*/
function getRawBody();

/**
* Returns parsed content of HTTP request body.
* @return mixed
* @throws InvalidRequestBodyException
*/
function getBody();
}
24 changes: 21 additions & 3 deletions src/Http/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You miss the assignment for bodyCallback.

{
$this->url = $url;
if ($query !== NULL) {
Expand All @@ -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;
}


Expand Down Expand Up @@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO rawbody should return rawbody, not parsed body

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

? This commit does not change getRawBody() result.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're using the same callback, that also parses the JSON for application/json, aren't you?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, you've added new parameter, sorry :)

}


/**
* Returns parsed content of HTTP request body.
* @return mixed
* @throws InvalidRequestBodyException
*/
public function getBody()
{
return $this->bodyCallback ? call_user_func($this->bodyCallback, $this) : NULL;
}


Expand Down
49 changes: 48 additions & 1 deletion src/Http/RequestFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace Nette\Http;

use Nette;
use Nette\Utils\Json;
use Nette\Utils\Strings;


Expand All @@ -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();
Copy link
Member

@dg dg Jun 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are also files… And files can have the same name as post variables. IMHO support for this type is not useful and is complicated to make it good.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Files are not send with application/x-www-form-urlencoded mime type but with multipart/form-data.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you right.

But still: it is useful?

});

$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
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strcasecmp is better

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean strncasecmp?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Its PHP shame that there is no startsWith function.

$body = $parser($request);
break;
}
}
}

return $body;
};

return new Request($url, NULL, $post, $files, $cookies, $headers, $method, $remoteAddr, $remoteHost, $rawBodyCallback, $bodyCallback);
}

}
18 changes: 18 additions & 0 deletions src/Http/exceptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/

namespace Nette\Http;

use Nette;


/**
* Exception is thrown when a request body can not be parsed.
*/
class InvalidRequestBodyException extends \RuntimeException
{
}
64 changes: 64 additions & 0 deletions tests/Http/RequestFactory.body.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

/**
* Test: Nette\Http\RequestFactory body parsing.
*/

use Nette\Http\InvalidRequestBodyException;
use Nette\Http\Request;
use Nette\Http\RequestFactory;
use Nette\Utils\JsonException;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';


$factory = new RequestFactory;

$setRawBody = function ($rawBody) {
$this->rawBodyCallback = function () use ($rawBody) {
return $rawBody;
};
};


test(function () use ($factory, $setRawBody) {
$_SERVER = [
'CONTENT_TYPE' => 'application/json',
];

$request = $factory->createHttpRequest();
$setRawBody->bindTo($request, Request::class)->__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/json',
];

$request = $factory->createHttpRequest();
$setRawBody->bindTo($request, Request::class)->__invoke('[');
Assert::same('[', $request->getRawBody());
$e = Assert::exception([$request, 'getBody'], InvalidRequestBodyException::class);
Assert::type(JsonException::class, $e->getPrevious());
});


test(function () use ($factory, $setRawBody) {
$_SERVER = [
'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
];

$_POST = [
'a' => 'b',
];

$request = $factory->createHttpRequest();
$setRawBody->bindTo($request, Request::class)->__invoke('a=c');
Assert::same('a=c', $request->getRawBody());
Assert::equal(['a' => 'b'], $request->body);
});