Contributte Middlewares
# Contributte Middlewares
The middlewares / relay conception is a strong pattern with many benefits.
# Content
# Setup
composer require contributte/middlewares
Register one of the given extensions (CompilerExtensions (opens new window))) in your config file. There are basically 2 single modes.
Standalone mode is best suitable for new projects with middleware architecture, works great with apitte (opens new window).
Nette mode is for integration to already running Nette projects, it overrides Nette\Application\Application
.
extensions:
# standalone mode
middleware: Contributte\Middlewares\DI\MiddlewaresExtension
# nette application mode
middleware: Contributte\Middlewares\DI\NetteMiddlewaresExtension
middleware:
debug: %debugMode%
2
3
4
5
6
7
8
9
# Modes
Main difference to nette/sandbox
application is in index.php
. You have to run
the middleware native IApplication::run()
.
$container->getByType(Contributte\Middlewares\Application\IApplication::class)->run();
That's all. The main purpose of this is to start via our application, not the default one Nette\Application\Application
.
# Application
AbstractApplication
adds a life cycle events you can interact with. There are 4 events:
startup
- triggered when$app->run()
is calledrequest
- triggered before the chain is callederror
- triggered when exceptions is occurredresponse
- triggered after the chain is called
You attach listener calling the method $app->addListener(type, callback)
.
services:
middleware.application:
setup:
- addListener(startup, [@logger, 'logStartup'])
- addListener(request, [@logger, 'logRequest'])
- addListener(error, [@logger, 'logError'])
- addListener(response, [@logger, 'logResponse'])
2
3
4
5
6
7
# Middlewares
Build your own middleware chain cannot be easier. Just place your middleware (services) under middleware
section.
It is pretty same as register new service in NEON
file.
# List
You can register list of middlewares like this:
middleware:
middlewares:
# Catch all exceptions
- Contributte\Middlewares\TracyMiddleware
# Your custom middlewares
- TrailingSlashMiddleware
- UuidMiddleware
- CspMiddleware
# Compatibility with Nette applications
- Contributte\Middlewares\PresenterMiddleware
2
3
4
5
6
7
8
9
10
11
12
# Tags
Or you can use tags at services.
services:
m1:
factory: App\Model\AppMiddleware1
tags: [middleware: [priority: 5]]
m2:
factory: App\Model\AppMiddleware2
tags: [middleware]
# default priority = 10
m3:
factory: App\Model\AppMiddleware3
tags: [middleware: [priority: 15]]
2
3
4
5
6
7
8
9
10
11
12
13
The final middleware list is:
AppMiddleware1
AppMiddleware2
AppMiddleware3
# IMiddleware
This is just interface for your middlewares.
namespace App;
use Contributte\Middlewares\IMiddleware;
final class MyCustomMiddleware implements IMiddleware
{
/**
* @param ServerRequestInterface $psr7Request
* @param ResponseInterface $psr7Response
* @param callable $next
* @return ResponseInterface
*/
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface
{
// Let's play
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Ready-to-use middlewares
At this time we have prepared a few middlewares:
# AutoBasePathMiddleware
It strips basePath from URL address and pass new URL (without basePath) to next middleware.
middleware:
middlewares:
# Catch all exceptions
- Contributte\Middlewares\TracyMiddleware
- Contributte\Middlewares\AutoBasePathMiddleware
# Your custom middlewares
- TrailingSlashMiddleware
- UuidMiddleware
- CspMiddleware
2
3
4
5
6
7
8
9
10
# BasePathMiddleware
It's quite similar with AutoBasePathMiddleware
, but you could define the basePath.
middleware:
middlewares:
# Catch all exceptions
- Contributte\Middlewares\TracyMiddleware
- Contributte\Middlewares\BasePathMiddleware(project/www)
# Your custom middlewares
- TrailingSlashMiddleware
- UuidMiddleware
- CspMiddleware
2
3
4
5
6
7
8
9
10
# BasicAuthMiddleware
Basic HTTP Authentication.
middleware:
middlewares:
- @basicAuth
services:
basicAuth:
class: Contributte\Middlewares\BasicAuthMiddleware
setup:
- addUser('user1', 'password1', true) // third parameter sets that password is not hashed
- addUser('user2', '$2y$10$p.U5q.BuQp02srggig.VDOqj5m7pE1rCwKavVQ3S2TrqWlkqu3qlC')
- addUser('user3', '$2y$10$bgievYVQMzsRn5Ysup.NKOVUk66aitAniAmts2EJAa91eqkAhohvC')
2
3
4
5
6
7
8
9
10
11
# BuilderMiddleware
Over this middleware you can build your own chain of middlewares.
middleware:
middlewares:
# Catch all exceptions
- Contributte\Middlewares\TracyMiddleware
- @builder
services:
builder:
class: Contributte\Middlewares\BuilderMiddleware
setup:
- add(TrailingSlashMiddleware())
- add(UuidMiddleware())
- add(CspMiddleware())
2
3
4
5
6
7
8
9
10
11
12
13
# EnforceHttpsMiddleware
Throw error if the request did not come from https. It is recommended behavior for apis (but not recommended for end-user applications).
middleware:
middlewares:
- Contributte\Middlewares\EnforceHttpsMiddleware
2
3
# LoggingMiddleware
Log uri for each request. Also removes login from that uri for security reasons.
middleware:
middlewares:
- Contributte\Middlewares\LoggingMiddleware($psr3Logger)
2
3
# MethodOverrideMiddleware
This middleware overrides HTTP method using X-HTTP-Method-Override
header. A typical use case would be when your API is behind some proxy/VPN which only allows some HTTP methods, for example only GET
and POST
. Sending header X-HTTP-Method-Override: PUT
will change the request method to PUT
.
middleware:
middlewares:
- Contributte\Middlewares\MethodOverrideMiddleware
2
3
# PresenterMiddleware
This middleware simulates original nette application behaviours. It converts Psr7Request to Nette\Application\Request
and process returned Nette\Application\Response
.
middleware:
middlewares:
# Catch all exceptions
- Contributte\Middlewares\TracyMiddleware
- @presenterMiddleware
services:
presenterMiddleware:
class: Contributte\Middlewares\PresenterMiddleware
setup:
- setErrorPresenter(Nette:Error)
- setCatchExceptions(%productionMode%)
2
3
4
5
6
7
8
9
10
11
12
PresenterMiddleware requires to run middlewares in Nette mode. Take a look at running [modes#application].
# TracyMiddleware
This middleware catch all exceptions and shows tracy dump. It can be instanced normally or via factory TracyMiddleware::factory($debugMode)
.
TryCatchMiddleware should be preferred by apis.
middleware:
middlewares:
- @tracy1
- @tracy2
services:
tracy1:
class: Contributte\Middlewares\TracyMiddleware
setup:
- enable()
- setMode(Tracy\Debugger::PRODUCTION)
- setEmail(cool@contributte.org)
tracy2:
class: Contributte\Middlewares\TracyMiddleware::factory(%debugMode%)
setup:
- setMode(Tracy\Debugger::PRODUCTION)
- setEmail(cool@contributte.org)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# TryCatchMiddleware
This middleware catches all exceptions thrown in other middlewares and informs user that application encountered an internal error. It should be registered first.
You could also pass PSR-3 compatible logger. Because all exceptions are handled by TryCatchMiddleware so exception handler could not log them itself.
middleware:
middlewares:
- @tryCatchMiddleware
services:
tryCatchMiddleware:
class: Contributte\Middlewares\TryCatchMiddleware
setup:
- setCatchExceptions(true) # affect if exceptions are catched in debug mode (they are always catched in production mode)
- setDebugMode(%debugMode%)
- setLogger(@Psr\Log\LoggerInterface, Psr\Log\LogLevel::ERROR)
2
3
4
5
6
7
8
9
10
11
Note that exceptions thrown elsewhere are generally caught in the ApiMiddleware and you can create your own errorHandler by implementing the IErrorHandler interface. This can also be used for the TryCatchMiddle if you wish for all your exceptions to be handled the same way.
# Utils
# ChainBuilder
$builder = new ChainBuilder();
$builder->add(function ($req, $res, callable $next) {
return $next($req, $res);
});
$builder->add(function ($req, $res, callable $next) {
return $next($req, $res);
});
$middleware = $builder->create();
2
3
4
5
6
7
8
# Lambda
Lambda utils class creates anonymous functions.
Lambda::leaf();
// ===
return function (RequestInterface $request, ResponseInterface $response) {
return $response;
};
2
3
4
5
Lambda::blank();
// ===
return function () {
};
2
3
4
# Examples
- https://github.com/contributte/playground/tree/master/apitte-fullstack (opens new window) (example project)
- https://github.com/contributte/apitte-skeleton (opens new window) (example skeleton)
- https://github.com/contributte/playground (opens new window) (playground)
- https://contributte.org/examples.html (opens new window) (more examples)