-
Notifications
You must be signed in to change notification settings - Fork 57
Provide http-interop compatibility #75
Provide http-interop compatibility #75
Conversation
`Next` will be called via its `process()` method when invoked by http-interop middleware; as such, it can invoke a separate method on Dispatch. This patch introduces `Dispatch::process()`, which then determines how to delegate the route provided. For non-http-interop middleware, it will raise an exception if the `Dispatch` instance does not have a response prototype injected, or if the request is a non-server-side request. When dispatching http-interop middleware, if an exception is caught, it raises exceptions for the same conditions, but otherwise delegates to the next error handler.
Extracted actual handler processing into: - dispatchCallableMiddleware() - dispatchInteropMiddleware() Each now tests for the handler type received and proxies to the appropriate method.
`Next` now implements `DelegateInterface`, to allow usage with PSR-15/http-interop middleware. For this to work, the following changes were made: - `Next` now optionally allows setting a response prototype. If none is set when `__invoke()` is first called, the instance will memoize the response. - `setResponsePrototype()` also sets the response prototype on the composed `Dispatch` instance. - `process()` was added, and programmed to operate exactly as `__invoke()`, with the following additional behaviors/behavior changes: - it calls `Dispatch::process()` instead of `Dispatch::__invoke()`. - if the queue is empty: - if no response prototype exists, an exception is thrown - if the request is not a server request, an exception is thrown - if one exists, that value will be passed to the `$done` handler - if delegating to the next in the queue, it uses its own `process()` method, and not the `__invoke()` method. - if the middleware dispatched does not return a response, and no response prototype is present, it raises an exception. - internal methods typehinting on `ServerRequestInterface` were updated to hint on `RequestInterface` instead, as they were only operating on methods defined in that looser interface.
`Next`: - Allows composing a `DelegateInterface` instance as the `$done` property. Invokes this differently based on whether it is callable or a delegate, and now only raises exceptions for missing response prototype or invalid request type when invoking a callable. `MiddlewarePipe`: - Now implements http-interop `ServerMiddlewareInterface` - Added response prototype composition; can set it, as well as test if one is already set. - Updated `pipe()` to allow piping http-interop middleware. - `__invoke()` now injects its `$response` argument as the `Next` response prototype. - Added `process()`: - Creates a `Next` instance using the `$delegate` provided. - Sets the `Next` response prototype if it has one composed itself. - Calls `Next::process()` with the provided request. - Returns the result if it is a response, but otherwise the composed response prototype. `Dispatch`: - Prior to dispatching `MiddlewarePipe` middleare, it now injects a `MiddlewarePipe` with its own response prototype if the pipeline does not yet compose one.
- `$done` verbiage is retained *within* the `__invoke()` and `process()` methods, but the property name and `dispatchDone()` methods are renamed.
…when piping when possible Backports the `CallableMiddlewareWrapper` from the dev-2.0.0 branch; `MiddlewarePipe::pipe()` will now decorate callable middleware *if a response prototype is present*; if not, it continues as normal.
If a Next instance is passed to CallableMiddlewareWrapper, do not close over it, as it is already invokable with the expected arguments.
Error middleware will be removed in 2.0.0, and will be handled specially anyways.
…ferently `pipe()` now detects if callable middleware has two arguments, with the second type-hinting on `DelegateInterface`; if so, it wraps it in a new class, `CallableInteropMiddlewareWrapper`, which simply proxies to the callable middleware.
Will not be used internally, but can be used by existing middleware in order to adapt it for http-interop.
Added extensive section on http-interop.
Since `MiddlewarePipe` now allows piping http-interop middleware, and version 2.0.0 will remove the `MiddlewareInterface`, these can be written as http-interop middleware instead.
…dler Each now implements `__invoke()`, and has it proxy to `process()` after first wrapping the `$next` argument in a `CallableDelegateDecorator`.
Primarily, to demonstrate http-interop middleware wherever possible.
Pinging @zendframework/community-review-team , @michaelmoussa , and @xtreamwayz for review, please. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had a few comments but overall LGTM.
> | ||
> Using the `$response` argument is unsafe when using delegation, as an inner | ||
> layer could return an entirely different response, ignoring any changes you | ||
> may have introduced previously. Additionally, hen manipulating the response |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ITYM "Additionally, when" unless poultry. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks - fixed locally, pushing shortly.
*/ | ||
private function isNotInteropMiddleware($handler, RequestInterface $request) | ||
{ | ||
if ($handler instanceof ServerMiddlewareInterface || $handler instanceof InteropMiddlewareInterface) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Considered this for readability?
if ($this->isInteropMiddleware($handler) {
return false;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed - nice suggestion!
); | ||
} | ||
|
||
printf("[%s] %s (%d)\n", get_class($throwable), $throwable->getMessage(), $throwable->getCode()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it not be better to record this as a user warning / error (perhaps using error_log(...)
rather than outputting it? Seems like an unlikely case, but in the event that it's hit, it could leak something sensitive as as plain output.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oof -- that was debug code. I'm a little surprised I missed it, which means that area is likely not tested. Removing now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like an invitation to bring the 100% test coverage hammer down in this repository too, muahahahaha! :)
Thanks, @michaelmoussa!
Suggested by @michaelmoussa
Forward port fixes due to feedback from @michaelmoussa on zendframework#75 Conflicts: src/Dispatch.php
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just the CallableInteropMiddlewareWrapper
needs a fix and probably some testing. But overall LGTM.
|
||
Starting in version 2.0.0, `MiddlewarePipe` *will no longer implement | ||
`Zend\Stratigility\MiddlewareInterface`, and only implement the http-interop | ||
`ServerMiddlewareInterface`*. This has several repurcussions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
repercussions ?
/** | ||
* Proxies to the underlying callable delegate to process a request. | ||
* | ||
* {@inheritDocs} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be {@inheritDoc}
.
@@ -57,37 +65,213 @@ public function __invoke( | |||
ResponseInterface $response, | |||
callable $next | |||
) { | |||
$handler = $route->handler; | |||
if (! $this->responsePrototype) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing method level docblock:
@return ResponseInterface
@throws Exception\InvalidRequestTypeException
@throws Exception\MissingResponsePrototypeException
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this particular case, neither of those two exceptions can occur:
- Since were passing the
$request
provided to__invoke()
,InvalidRequestTypeException
will not be raised, as we have a server-side request. - Since we received a response, the response prototype will be present, so
MissingResponseProtototypeException
will not be thrown, either.
I will add the missing @return
annotation, however!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, @throws only makes sense when the method itself throws that exception, not some dependency.
*/ | ||
public function process(ServerRequestInterface $request, DelegateInterface $delegate) | ||
{ | ||
return $middleware($request, $delegate); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return $this->middleware($request, $delegate);
Probably lacks testing as well.
use Zend\Stratigility\Exception\MissingDelegateException; | ||
use Zend\Stratigility\Exception\MissingResponseException; | ||
use Zend\Stratigility\MiddlewareInterface; | ||
use Zend\Stratigility\Utils; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused imports
use Zend\Escaper\Escaper;
use Zend\Stratigility\Exception\MissingDelegateException;
use Zend\Stratigility\Utils;
*/ | ||
public function __construct($path, callable $handler) | ||
public function __construct($path, $handler) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I notice an inconsistent use of @throws
in dockblocks so I'm not sure if they are required or not.
e.g. in this class the constructor lacks @throws
in its dockblock whereas __get()
has it.
…aled by test Updated `process()` to assign local `$middleware` variable to `$middleware` property value.
Ensured all behaviors work as expected.
Forward port updates from zendframework#75 Conflicts: src/Dispatch.php src/Route.php
@xtreamwayz I've addressed all your points of feedback; thanks! Additionally, wrote tests for the |
LGTM. |
These needed to be passed to ReflectionMethod, with each element of the array as discrete arguments; previously, they were passed to ReflectionFunction, which failed as it cannot handle arbitrary callables.
Will likely set the response prototype during instantation in Expressive, and then use that as the default response if none is provided to `run()`; as such, it cannot be private visibility.
Forward port `$responsePrototype` visibility from zendframework#75
Merge develop, including #75 changes.
This patch provides compatibility with http-interop 0.2.0, via the following features:
MiddlewarePipe
:now implements
ServerMiddlewareInterface
.now allows piping http-interop
ServerMiddlewareInterface
andMiddlewareInterface
instances.now allows piping callable middleware with the signature:
Now exposes a
setResponsePrototype()
method; when set, callable middleware implementing the legacy interface will be decorated asServerMiddlewareInterface
instances.Next
:DelegateInterface
.Dispatch
:process()
method mimicing theDelegateInterface
; this method is used byNext
when dispatchingServerMiddlewareInterface
instances.Additionally, this patch provides decorator classes for decorating legacy callable middleware, callable middleware compatible with
ServerMiddlewareInterface
, and callable delegates. This latter feature can be used to adapt existing callable middleware classes to implementServerMiddlewareInterface
:Finally, the patch provides migration documentation to both v1.3 and v2.0. Another patch will be submitted shortly against the dev-2.0.0 branch that incorporates the patches from this branch, with updates to provide the final 2.0.0 functionality around http-interop compatibility.