Skip to content

Replace Response class with PSR-7 Response class - #152

Merged
clue merged 4 commits into
reactphp:masterfrom
legionth:handle-psr7-responses
Mar 30, 2017
Merged

Replace Response class with PSR-7 Response class#152
clue merged 4 commits into
reactphp:masterfrom
legionth:handle-psr7-responses

Conversation

@legionth

@legionthlegionth commented Mar 26, 2017

Copy link
Copy Markdown
Contributor

This is big PR. I recommend to try reviewing the single commits here.

The Response class will be replaced with an that implements the PSR-7 ResponseInterface.
So the callback function only needs the request object to be called. A PSR-7 Response object will now be returned. Alternatively a Promise can be returned in this function, but this promise MUST resolve a PSR-7 Response. If the response need time be created, a promise must be used (checkout examples/04-stream-request.php).

The new Response extends from RingCentral\Psr7\Response but adds the possibility to add an ReadstreamInterface implemnation as body.

Refs #28
Builds on top of #146

@legionthlegionth changed the title Replace Response class with PSR-7 ResponseReplace Response class with PSR-7 Response classMar 26, 2017

@WyriHaximusWyriHaximus left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

👍 for your work, found some things, any comment that occurs more then once is to point out all occurrences for the given comment.

Comment threadREADME.md Outdated
$response->writeHead(200, array(
'Date' => date('D, d M Y H:i:s T')
));
$server = new Server($socket, function (RequestInterface $request) use ($loop) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No need for $loop here

Comment threadREADME.md Outdated
$response->writeHead(200, array(
'Date' => array()
));
$server = new Server($socket, function (RequestInterface $request) use ($loop) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No need for $loop here

Comment threadREADME.md Outdated
$response->writeHead(200, array(
'X-Powered-By' => 'PHP 3'
));
$server = new Server($socket, function (RequestInterface $request) use ($loop) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No need for $loop here

Comment threadREADME.md Outdated
$response->writeHead(200, array(
'X-Powered-By' => array()
));
$server = new Server($socket, function (RequestInterface $request) use ($loop) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No need for $loop here

Comment threadsrc/Server.php Outdated
$response = $response->withBody($body);
}

if ($response->hasHeader('Transfer-Encoding')) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Shouldn't be a second check to ensure the contents of this header is chunked before we run the body through the chunked encoder? In case we add transfer encoding X or Y in the future?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This is necessary at moment because nothing else would be allowed here. But have changed it to make this more ovious.

Comment threadREADME.md Outdated
See also [example #3](examples) for more details.

The constructor is internal, you SHOULD NOT call this yourself.
The `Server` is responsible for emitting `Request` and `Response` objects.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This line and the 3 lines above it are no longer relevant and ended up in middle of a code example, see https://github.com/legionth/http/blob/c0aef5da52404f458222acf3554c5f591c63395d/README.md#response

Comment threadREADME.md Outdated
@@ -156,22 +169,39 @@ gives you access to the incoming request body as the individual chunks arrive:

```php
$http = new Server($socket, function (RequestInterface $request, Response $response) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We don't pass $response anymore since this PR

Comment threadREADME.md Outdated
@@ -118,9 +126,14 @@ and will be passed to the callback function like this.

```php
$http = new Server($socket, function (RequestInterface $request, Response $response) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We don't pass $response anymore since this PR

Comment threadREADME.md Outdated
@@ -71,8 +76,11 @@ $socket = new React\Socket\SecureServer($socket, $loop, array(
));

$http = new Server($socket, function (RequestInterface $request, Response $response) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We don't pass $response anymore since this PR

Comment threadREADME.md Outdated
@@ -53,8 +55,11 @@ constructor with the respective [request](#request) and
$socket = new React\Socket\Server(8080, $loop);

$http = new Server($socket, function (RequestInterface $request, Response $response) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We don't pass $response anymore since this PR

Comment threadsrc/Server.php Outdated
use Psr\Http\Message\ResponseInterface;
use RingCentral;
use React\Stream\ReadableStream;
use RingCentral\Psr7\Response;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Shouldn't we be using our own response class here, as it extends that class? Also we have a class name conflict: https://travis-ci.org/reactphp/http/jobs/215140364#L237

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yea, must be a leftover thank you for spotting this.

Comment threadsrc/Server.php Outdated
$promise = new Promise(function ($resolve, $reject) use ($promise) {
$resolve($promise);
});
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These 6 lines can be shortened to:

$promise = \React\Promise\resolve($callback($request));

Additional benefit: supports also foreign thenables.

Another question: What happens if $callback synchronously throws an exception/throwable? This should probably turned into a rejected promises:

try {
$promise = \React\Promise\resolve($callback($request));
} catch (\Throwable$e) {
$promise = \React\Promise\reject($e);
} catch (\Exception$e) {
$promise = \React\Promise\reject($e);
}

I'd prefer this over just letting the exception bubble up because it would handle exceptions in the same ways as if $callback returns a rejected promises (see also my next comment).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The problem with $promise = \React\Promise\resolve($callback($request)); is that would have to use try-catch. With my lines the Exception would be handled by the Promise and will be recognized as rejected. But thanks for the line didn't knew it ;)

Comment threadsrc/Server.php
}
$that->handleResponse($conn, $response, $request->getProtocolVersion());
}
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This must also handle a rejected promise returned from $callback.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

You are right. Added something to cover this. Have a look 👍

Comment threadREADME.md Outdated
$http = new Server($socket, function (RequestInterface $request) {
return new Response(
200,
array('Content-Type' => 'text/plain')

@andigandigMar 26, 2017

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

misses trailing ,
same thing happening below

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thx for spotting this.

@clueclue left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Love this feature and I'm happy to get this in once the outstanding change requests are resolved! :shipit:

@WyriHaximus

Copy link
Copy Markdown
Member

@clue yeah can't wait for v0.7.0 to be released and start adapting my work for v0.8.0 🎉 . This, v0.7.0, will be a huge milestone on it's own 👍

@andigandig mentioned this pull request Mar 26, 2017
@legionth
legionthforce-pushed the handle-psr7-responses branch 3 times, most recently from 57cc76c to fd1affbCompareMarch 27, 2017 22:31
@legionth

Copy link
Copy Markdown
ContributorAuthor

Thank you for all your rewievs! I rebased the branch. This didn't really changed the code much, but I hope this will make clearer what really changed.

Happy to hear your opinons about this.

Comment threadREADME.md Outdated

The `Response` class is responsible for streaming the outgoing response body.
The callback function passed to the constructor of the [Server](#server)
is responsible to return process a response, which will be delivered to the client.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

…is responsible for processing the request and returning a response, which…

Comment threadREADME.md Outdated
The `Response` class is responsible for streaming the outgoing response body.
The callback function passed to the constructor of the [Server](#server)
is responsible to return process a response, which will be delivered to the client.
This function MUST return either a implementation of the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

…MUST return an instance implementing…

Comment threadREADME.md Outdated
which implements the `PSR-7 RequestInterface` in this project.
We use instantiation of this class in our projects,
but feel free to use any implemantation of the
`PSR-7 RequestInterface` you prefer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would suggest moving this to above the example, what do you think?

Comment threadREADME.md Outdated

The `Response` will automatically use the same HTTP protocol version as the
corresponding `Request`.
If your response takes time to be processed you SHOULD use a `ReactPHP Promise`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why is this a SHOULD, what happens if consumers ignore this?

Comment threadREADME.md Outdated
This needs to be a promise, because the request body may needs time to be completed.
The `ReactPHP Promise` will resolve in a `Response` object when the request
body ends.
Always use the `ReactPHP Promise` when your

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

…when?

Comment threadREADME.md Outdated
This is just a example you could use of the streaming,
you could also send a big amount of data via little chunks
or use it for body data that needs to calculated.
Use the oppertunties of the `ReactPHP Streams`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Last sentence can probably be omitted?

Comment threadREADME.md Outdated
'Date' => date('D, d M Y H:i:s T')
));
$server = new Server($socket, function (RequestInterface $request) {
return new \RingCentral\Psr7\Response(200, array('Date' => date('D, d M Y H:i:s T')));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

class name?

Comment threadREADME.md Outdated
200,
array(
'Content-Type' => 'text/plain',
'Content-Length' => strlen($data)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There's room for improvement here 👍 Is this really still necessary? 👍

Comment threadREADME.md Outdated
Make sure to catch these `Exceptions` to create own response messages.

After the return in the callback function the response will be processed by the `Server`.
The `Server` will add the protocol version of the reequest, so you don't have to.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

typo: request

Comment threadREADME.md Outdated

An unhandled Exception in the code of the callback function will result
in an `500 Internal Server Error` message.
Make sure to catch these `Exceptions` to create own response messages.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What happens if the callback function returns the wrong value?

Comment threadsrc/Server.php Outdated
$that->handleResponse($conn, $response, $request->getProtocolVersion());
},
function ($ex) use ($that, $conn) {
$that->emit('error', array($ex));

@jsorjsorMar 28, 2017

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note, that promises can be rejected with a non-exception (there is a pending PR to change that: reactphp/promise#93). The docs suggest a typehint against Exception for the error event listeners.

$http->on('error', function (Exception$e) {
});

Maybe check against \Exception/ \Throwable here and turn non-exceptions into a \Exception?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Added code to cover this behaviour.

Comment threadsrc/Server.php
use Psr\Http\Message\ResponseInterface;
use RingCentral;
use React\Stream\ReadableStream;
use React\Promise\Promise;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since you're starting to use react/promise explicitely, i recommend adding it to the composer.json. At the moment react/promise is only indirectly required via react/socket.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Added to react/promise to composer.json

@legionth
legionthforce-pushed the handle-psr7-responses branch from fd1affb to b71e67bCompareMarch 28, 2017 10:51
@legionth

Copy link
Copy Markdown
ContributorAuthor

Ping @clue . I fixed the README, also found some mistakes myself :) . Have a look.

Comment threadsrc/Server.php Outdated
$that->handleResponse($conn, $response, $request->getProtocolVersion());
},
function ($ex) use ($that, $conn) {
if (!$ex instanceof \Exception) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should test against \Throwable too.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Keep in mind that the error event is type-hinted as Exception though!

Comment threadsrc/Server.php Outdated
},
function ($ex) use ($that, $conn) {
if (!$ex instanceof \Exception) {
$ex = new \InvalidArgumentException('Unknown rejection type');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This exception message is very generic. Suggestion:

$ex = new \InvalidArgumentException(
sprintf(
'The response callback returned a rejected promise with a reason of type "%s" instead of a \Throwable or \Exception instance.'is_object($ex) ? get_class($ex) : gettype($ex)
)
);

Comment threadsrc/Server.php Outdated
$promise->then(
function ($response) use ($that, $conn, $request) {
if (!$response instanceof ResponseInterface) {
$that->emit('error', array(new \InvalidArgumentException('Invalid response type')));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Very generic exception message. Suggestion:

$ex = new \InvalidArgumentException(
sprintf(
'The response callback is expected to return an object implementing Psr\Http\Message\ResponseInterface, but returned "%s" instead.'is_object($response) ? get_class($response) : gettype($response)
)
);

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Added a more useful message. Have a look.

Comment threadsrc/Server.php Outdated
},
function ($ex) use ($that, $conn) {
if (!$ex instanceof \Exception) {
$ex = new \InvalidArgumentException('Unknown rejection type');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Very generic exception message. Suggestion:

$ex = new \InvalidArgumentException(
sprintf(
'The response callback returned a rejected promise with a reason of type "%s" instead of a \Throwable or \Exception instance.'is_object($ex) ? get_class($ex) : gettype($ex)
)
);

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Added a more useful message. Have a look.

Comment threadsrc/Server.php Outdated
$that->handleResponse($conn, $response, $request->getProtocolVersion());
},
function ($ex) use ($that, $conn) {
if (!$ex instanceof \Exception) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should test against \Throwable too.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks for the review.I reconsidered this and changed the code. The error-event on the Server should always be an Exception.
In the newest changes a RuntimeExpception will always be created, and a thrown exception will be added as previous to this RuntimeException.
Checkout the documentation and the code.

Comment threadREADME.md Outdated
@@ -270,49 +353,58 @@ will automatically use chunked transfer encoding and send the respective header
If you know the length of your body, you MAY specify it like this instead:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this paragraph still apply? I see some room for improvement here with the updated API 👍

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yes this does still apply, at least for streams. Changed the documenation, have a look.

Comment threadREADME.md
```

If you do not want to send this header at all, you can use an empty array as
value like this:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

array?

Comment threadexamples/01-hello-world.php Outdated
return new Response(
200,
array(
'Content-Length' => strlen("Hello world\n"),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

See above comment, is this line really (still) necessary?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

You are right. Change it in the newest commit.

Comment threadsrc/Server.php

if ($response->getHeaderLine('Transfer-Encoding') === 'chunked') {
$stream = new ChunkedEncoder($body);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Isn't this header always removed or am I missing something? Also, who should be in control of this behavior in this first place? This whole method looks unneeded complex to me, sending a constant string body could be simplified, what do you think?

@legionthlegionthMar 30, 2017

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Changed structure

@legionth
legionthforce-pushed the handle-psr7-responses branch from b71e67b to afc1bcdCompareMarch 29, 2017 22:14
Comment threadREADME.md
'X-Powered-By' => 'PHP 3'
));
$server = new Server($socket, function (RequestInterface $request) {
return new Response(200, array('X-Powered-By' => 'PHP 3'));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

👍 on using PHP 3 as example, although I would have approved of 6 as well 😆

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Well, I can still change it while I'm on it. Just used the same example as before 😅

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If it where up to me: go for it 😎

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1 for PHP 6!

@legionth
legionthforce-pushed the handle-psr7-responses branch 2 times, most recently from 8b954bc to 64839a4CompareMarch 30, 2017 11:56
@legionth
legionthforce-pushed the handle-psr7-responses branch from 64839a4 to ce27ec6CompareMarch 30, 2017 12:51

@clueclue left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Awesome! :shipit: 🎉

@clue
clue merged commit e734181 into reactphp:masterMar 30, 2017
Comment threadsrc/Server.php
function ($error) use ($that, $conn) {
$message = 'The response callback is expected to resolve with an object implementing Psr\Http\Message\ResponseInterface, but rejected with "%s" instead.';
$message = sprintf($message, is_object($error) ? get_class($error) : gettype($error));
$exception = new \RuntimeException($message, null, $error instanceof \Exception ? $error : null);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You missed \Throwable support here, opened #155.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@legionth@WyriHaximus@jsor@andig@clue