[PSR-15] Falsehoods About The Client-side

468 views
Skip to first unread message

Michael Mayer

unread,
Oct 29, 2016, 5:47:54 PM10/29/16
to PHP Framework Interoperability Group
Hi everyone,

I'm a little bit surprised to see so many suggestions about how ServerMiddlewareInterface and ClientMiddlewareInterface should look like, obviously without much knowledge about the client-side.

interface MiddlewareInterface {}

interface
ClientMiddlewareInterface extends MiddlewareInterface
{
    public function process(RequestInterface $request, DelegateInterface $next);
}

interface ServerMiddlewareInterface extends MiddlewareInterface
{
   
public function process(ServerRequestInterface $request, DelegateInterface $frame);
}

interface DelegateInterface
{
   
public function next(RequestInterface $request);
}




Here are some falsehoods about the client-side, the list seems, by far, not exhaustive.

1. HTTP Clients return their results synchronously
It would be very clumsy to load a web page nowadays with all its assets sequentially. Thus HTTP clients like Guzzle use asynchronous techniques like Promises (e.g. Guzzle Promises), callbacks (e.g. node.js), generator based control flow, etc.

An example:
$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org');
$promise = $client->sendAsync($request)->then(function ($response) {
    echo 'I completed! ' . $response->getBody();
});

2. Client Middlewares just transform Requests to Responses, doing it synchronously is not a problem
Maybe, this is appropriate on the server side, but not on the client. Just think about a Middleware which adds cookies to requests.

return function ($request, array $options) use ($handler) {
   
// …
    $request
= $cookieJar->withCookieHeader($request);
   
return $handler($request, $options)
       
->then(function ($response) use ($cookieJar, $request) {
            $cookieJar
->extractCookies($request, $response);
           
return $response;
       
}
   
);
};

The Middleware have to:
  1. add the current cookie to the header: ->withCookieHeader($request)
  2. call the delegate: $handler($request, $options)
  3. extract the cookie
As the delegate returns a promise, we need to: $handler()->then(…).
Can we do better, e.g. use a sync delegate/handler? Nope, this would lead to call $promise->wait() within the delegate, which isn't performant. 

3. Client Middlewares call their delegate almost once
For the client-side it's absolutely valid to retry a request, e.g. GuzzleHttp/RetryMiddleware

4. Client Middlewares don't need to create requests, they can modify the given $request
Just think about an authorization middleware, e.g. for OAuth or similar.
It would be dangerous or at least very odd to reuse $request for login endpoints:
class AuthMiddleware implements ClientMiddlewareInterface
{
   
// …
   
public function process(RequestInterface $request, DelegateInterface $next)
   
{
       
if ($this->accessToken === null) {
            $loginRequest
= $this->createLoginRequest();
            $this
->accessToken = $next->next($loginRequest)->getBody()->getContents();
       
}

        $request
= $request->withHeader('Authorization', $this->accessToken);
       
return $next($request);
   
}


   
private function createLoginRequest()
   
{
        $req
= $this->requestFactory->createRequest('POST', '/login');
       
// add $login, $password…
       
return $req;
   
}
}

This was just some food for thought, hopefully pushing PSR-15 a bit forward.

Best regards,
Michael

Woody Gilk

unread,
Oct 31, 2016, 9:50:39 AM10/31/16
to PHP Framework Interoperability Group
Michael,

Thanks for bringing this up. For historical context, I have always thought of "client middleware" as middleware that does not require the additional methods defined in ServerRequestInterface. The consideration of async vs sync requests was never something that entered my mind.

Reviewing the Guzzle docs again (as the most mature PSR-7 client-focused package I know of) it certainly appears that the current PSR-15 signatures would not work there. That is obviously a serious flaw.

However, since there is not currently a ratified PSR for async, perhaps the best thing to do would be to repurpose PSR-15 to be entirely server-focused middleware? I would rather defer the creation of a spec for client middleware until the async PSR is complete.

Would that be an acceptable situation for you? I don't want to create something that async clients will never be able to use.

Regards,

--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/f57bb59a-5784-4e6d-aaea-bd738e49a00b%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

David Négrier

unread,
Nov 2, 2016, 10:11:09 AM11/2/16
to PHP Framework Interoperability Group
Hey Woody,

I was speaking with Matthieu Napoli and Joel Wurtz (HTTPlug contributor) about PSR-15 last week at Paris ForumPHP. If I correcly remember the discussion we had, the general consensus was that PSR-15 should indeed focus on the server side middlewares.
Client middlewares are a different kind of beast. I don't think you will face a big pushback if you decide to focus PSR-15 on server side middleware. This is what people expect from PSR-15.

Furthermore, HTTPlug is already trying to standardize client side middlewares (see http://docs.php-http.org/en/latest/plugins/introduction.html) and they may want to push a PSR based on their work in the future.

++
David.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.

Michael Mayer

unread,
Nov 2, 2016, 6:28:59 PM11/2/16
to PHP Framework Interoperability Group
Woody,

I have no experience in standardization processes, hence everything is acceptable to me. However, I see an additional benefit of two PSRs: it would be much easier to superseded them independently – hopefully that does never happen ;)

Best regards,
Michael
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.

Matthieu Napoli

unread,
Nov 3, 2016, 3:47:15 AM11/3/16
to PHP Framework Interoperability Group
Definitely agree with making PSR-15 about server side middlewares only. Joel mentioned several reasons why client-side middlewares needed something different (e.g. the ability to reference the root middleware, for example to follow a redirect through the whole stack again).

Also that would maybe mean we can get rid of the dual "MiddlewareInterface" and "ServerMiddlewareInterface"?

Matthieu

Woody Gilk

unread,
Nov 3, 2016, 8:51:53 AM11/3/16
to PHP Framework Interoperability Group
On Thu, Nov 3, 2016 at 2:47 AM, Matthieu Napoli <matt...@mnapoli.fr> wrote:
Also that would maybe mean we can get rid of the dual "MiddlewareInterface" and "ServerMiddlewareInterface"?

It would indeed, which would solve one of the most contentious points about PSR-15.

Unlikely that I will have time to work on this before next week, but expect an update shortly.

Michael Mayer

unread,
Nov 3, 2016, 9:38:59 AM11/3/16
to PHP Framework Interoperability Group
Hi Matthieu,

calling the root middleware seem to be a valid use case for server middlewares too:
If a middleware have to create (virgin) Requests, then calling the root middleware makes a lot more sense than calling the next middleware.

May you give me a hint where Joel mentioned this? – I can't find it.

Michael

Matthieu Napoli

unread,
Nov 3, 2016, 10:26:17 AM11/3/16
to php...@googlegroups.com
Ah sorry it was a discussion IRL :)

Calling the root middleware for the server apps is a much less widespread use case AFAIK. In existing implementations and de-facto standards (HttpKernelInterface and "PSR-7 middlewares") there is no such thing, so (IMO) it wouldn't make sense to create such a big break for no gain in most cases.

If a server-side middleware needs to call the root middleware it can still be injected in its constructor (dependency injection). The point of Joel was that it was a much more common scenario for HTTP clients IIRC.
You received this message because you are subscribed to a topic in the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and all its topics, send an email to php-fig+u...@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Joel Wurtz

unread,
Nov 3, 2016, 12:58:23 PM11/3/16
to PHP Framework Interoperability Group

Hi,

David and Matthieu already give my point of view, however there is still some things to consider:

At HttpPlug we wrote a lot of client side Middleware from what we have done we organise them in 3 categories (cf http://docs.php-http.org/en/latest/plugins/introduction.html part about ordering middleware) :

  • Request/Response modifier middleware: they mostly set and add new values into the request or response, like an Authentication or Cookie Middleware
  • Control Flow middleware: does not modify the request or response but can change the chain of middleware for the request based on the value, like a Redirection, Retry or Exception Middleware
  • Observer middleware (Read only): don’t touch the request or modify the flow, only used for viewing what happens, like a Logger or History Middleware

IMO, on the server side it should be the same “categories” of middleware. From what i imagine, implementation of “Observer middlewares” will be the same for server and client. However “Control Flow” and “Request/Response Modifier”
middlewares will not have the same implementations, as on the server you read the request to write the response and on the client you write the request to read the response.

As an example, a compression Middleware (gzip) will encode the response for a Server middleware but will decode it for a Client middleware

Creating a Middleware that works on boths worlds imply, IMO:

  • To work only with the “MessageInterface”
  • To force people implementing middleware to never do anything after calling the next method on the $frame

As an example we could have the following middleware

class GzipEncoderMiddleware implements MiddlewareInterface
{
    public function process(MessageInterface $message, DelegateInterface $frame)
    {
        $message = $message->withBody(new GzipStream($message->getBody()));

        return $frame->next($message);
    }
}

Then this middleware could be use for encoding the request in a client side approach or encoding the response in a server side approach.
But even there they will be many problems as the HTTP RFC does not always use the same headers for specifiying the response or the request so this will be in fact not usable.

IMO, Having a PSR15 that works on server and client is not something that should be considered, as it will overcomplicate the standard for something that cannot be shared (or will be extremly difficult). Futhermore i think this will confuse
many people that works on the client side.

For Mathieu and Michael:

Calling the root middleware for the server apps is a much less widespread use case AFAIK. In existing implementations and de-facto standards (HttpKernelInterface and “PSR-7 middlewares”) there is no such thing, so (IMO) it wouldn’t make sense to create such a big break for no gain in most cases.

I’m not sure, we may consider the HMVC approach of symfony (the sub request system) as a way to pass a new request to the root middleware.

However after rethinking why we have include the $root middleware in our system, i think this was a mistake, it make things simple only for a single implementation (the Retry one) but overcomplicate the interface for all others implementations that never need the root.

Our chain of middleware (referenced by the root / first middleware) is unique at each new request. Having this unicity (at the request level) is necessary for the Retry plugin as we can use it as a reference to detect circular redirection (and also avoid overlap with another call). There is certainly a better way to transform a RequestInterface object into a unique reference, however i never had the time to look more at this.

Woody Gilk

unread,
Nov 6, 2016, 4:26:39 PM11/6/16
to PHP Framework Interoperability Group
The process of moving focus to server-only can be seen in the following PRs:

https://github.com/http-interop/http-middleware/pull/25

Again, I thank you for your feedback and am grateful for it.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+unsubscribe@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages