imrgrgs/psr7-middlewares

兼容PSR-7的HTTP中间件集合

dev-master 2019-08-15 13:41 UTC

This package is auto-updated.

Last update: 2024-09-16 01:24:36 UTC


README

PSR-7中间件的集合。

要求

  • PHP >= 5.5
  • 一个PSR-7 HTTP消息实现,例如 zend-diactoros
  • 一个与以下签名的PSR-7中间件分发器兼容
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

function (RequestInterface $request, ResponseInterface $response, callable $next) {
    // ...
}

因此,您可以使用这些中间件与以下进行配合使用

安装

该软件包可以通过Composer安装和自动加载,地址为 imrgrgs/psr7-middlewares

$ composer require imrgrgs/psr7-middlewares

使用示例

use Psr7Middlewares\Middleware;

use Relay\RelayBuilder;
use Zend\Diactoros\Response;
use Zend\Diactoros\ServerRequestFactory;
use Zend\Diactoros\Stream;

//Set a stream factory used by some middlewares
//(Required only if Zend\Diactoros\Stream is not detected)
Middleware::setStreamFactory(function ($file, $mode) {
    return new Stream($file, $mode);
});

//Create a relay dispatcher and add some middlewares:
$relay = new RelayBuilder();

$dispatcher = $relay->newInstance([

    //Calculate the response time
    Middleware::responseTime(),

    //Add an Uuid to request
    Middleware::uuid(),

    //Minify the result
    Middleware::minify(),

    //Handle errors
    Middleware::errorHandler()->catchExceptions(true),

    //Override the method using X-Http-Method-Override header
    Middleware::methodOverride(),

    //Parse the request payload
    Middleware::payload(),

    //Remove the path prefix
    Middleware::basePath('/my-site/web'),

    //Remove the trailing slash
    Middleware::trailingSlash(),

    //Digest authentication
    Middleware::digestAuthentication(['username' => 'password']),

    //Get the client ip
    Middleware::clientIp(),

    //Allow only some ips
    Middleware::firewall(['127.0.0.*']),

    //Detects the user preferred language
    Middleware::languageNegotiator(['gl', 'es', 'en']),

    //Detects the format
    Middleware::formatNegotiator(),

    //Adds the php debug bar
    Middleware::debugBar(),

    //Execute fast route
    Middleware::fastRoute($app->get('dispatcher')),
]);

$response = $dispatcher(ServerRequestFactory::fromGlobals(), new Response());

可用的中间件

AccessLog

使用Apache的访问日志格式(https://httpd.apache.ac.cn/docs/2.4/logs.html#accesslog)为每个请求生成访问日志。此中间件需要一个Psr日志实现,例如 monolog

use Psr7Middlewares\Middleware;
use Monolog\Logger;
use Monolog\Handler\ErrorLogHandler;

//Create the logger
$logger = new Logger('access');
$logger->pushHandler(new ErrorLogHandler());

$middlewares = [

    //Required to get the Ip
    Middleware::ClientIp(),

    Middleware::AccessLog($logger) //Instance of Psr\Log\LoggerInterface
        ->combined(true)           //(optional) To use the Combined Log Format instead the Common Log Format
];

AttributeMapper

将中间件特定属性映射到所需的名称下的常规请求属性

use Psr7Middlewares\Middleware;

$middlewares = [

    //Example with authentication
    Middleware::BasicAuthentication([
        'username1' => 'password1',
        'username2' => 'password2'
    ]),

    //Map the key used by this middleware
    Middleware::attributeMapper([
        Middleware\BasicAuthentication::KEY => 'auth:username'
    ]),

    function ($request, $response, $next) {
        //We can get the username as usual
        $username = BasicAuthentication::getUsername($request);

        //But also using the "auth:username" attribute name.
        assert($username === $request->getAttribute('auth:username'));

        return $next($request, $response);
    }
];

AuraRouter

Aura.Router (3.x)用作中间件

use Psr7Middlewares\Middleware;
use Psr7Middlewares\Middleware\AuraRouter;
use Aura\Router\RouterContainer;

//Create the router
$router = new RouterContainer();

$map = $router->getMap();

$map->get('hello', '/hello/{name}', function ($request, $response, $myApp) {

    //The route parameters are stored as attributes
    $name = $request->getAttribute('name');

    //You can get also the route instance
    $route = AuraRouter::getRoute($request);

    //Write directly in the response's body
    $response->getBody()->write('Hello '.$name);

    //or echo the output (it will be captured and writted into body)
    echo 'Hello world';

    //or return a string
    return 'Hello world';

    //or return a new response
    return $response->withStatus(200);
});

//Add to the dispatcher
$middlewares = [

    Middleware::AuraRouter($router) //Instance of Aura\Router\RouterContainer
        ->arguments($myApp)         //(optional) append more arguments to the controller
];

AuraSession

使用请求创建一个新的Aura.Session实例

use Psr7Middlewares\Middleware;
use Psr7Middlewares\Middleware\AuraSession;

$middlewares = [

    Middleware::AuraSession(),
        ->factory($sessionFactory) //(optional) Intance of Aura\Session\SessionFactory
        ->name('my-session-name'), //(optional) custom session name

    function ($request, $response, $next) {
        //Get the session instance
        $session = AuraSession::getSession($request);

        return $response;
    }
];

BasePath

从请求的uri路径中移除前缀。这对于与路由器配合使用很有用,如果网站根目录位于子目录中。例如,如果您的网站根目录为 /web/public,则uri为 /web/public/post/34 的请求将转换为 /post/34。您可以提供要移除的前缀,或者让中间件自动检测。在路由器中,您可以检索移除的前缀或用于生成更多基于基本路径的URL的可调用对象。

use Psr7Middlewares\Middleware;
use Psr7Middlewares\Middleware\BasePath;

$middlewares = [

    Middleware::BasePath('/web/public') // (optional) The path to remove...
        ->autodetect(true),             // (optional) ...or/and autodetect the base path

    function ($request, $response, $next) {
        //Get the removed prefix
        $basePath = BasePath::getBasePath($request);

        //Get a callable to generate full paths
        $generator = BasePath::getGenerator($request);

        $generator('/other/path'); // /web/public/other/path

        return $response;
    }
];

BasicAuthentication

实现基本http认证。您必须提供一个包含所有用户和密码的数组

use Psr7Middlewares\Middleware;

$middlewares = [

    Middleware::BasicAuthentication([
            'username1' => 'password1',
            'username2' => 'password2'
        ])
        ->realm('My realm'), //(optional) change the realm value

    function ($request, $response, $next) {
        $username = BasicAuthentication::getUsername($request);

        return $next($request, $response);
    }
];

BlockSpam

使用piwik/referrer-spam-blacklist列表来阻止引用垃圾邮件

use Psr7Middlewares\Middleware;

$middlewares = [

    Middleware::BlockSpam('spammers.txt'), //(optional) to set a custom spammers list instead the piwik's list
];

Cache

需要micheh/psr7-cache。在缓存中保存响应的头部并返回304响应(未修改),如果请求已缓存。它还向响应添加 Cache-ControlLast-Modified 标头。您需要一个与psr-6兼容的缓存库。

use Psr7Middlewares\Middleware;

$middlewares = [

    Middleware::Cache(new Psr6CachePool()) //the PSR-6 cache implementation
        ->cacheControl('max-age=3600'),    //(optional) to add this Cache-Control header to all responses
];

ClientIp

检测客户端IP(s)。

use Psr7Middlewares\Middleware;
use Psr7Middlewares\Middleware\ClientIp;

$middlewares = [

    Middleware::ClientIp()
        ->remote()  // (optional) Hack to get the ip from localhost environment
        ->headers([ // (optional) to change the trusted headers
            'Client-Ip',
            'X-Forwarded-For',
            'X-Forwarded'
        ]),

    function ($request, $response, $next) {
        //Get the user ip
        $ip = ClientIp::getIp($request);

        //Get all ips found in the headers
        $all_ips = ClientIp::getIps($request);

        return $next($request, $response);
    }
];

Cors

使用 neomerx/cors-psr7

use Psr7Middlewares\Middleware;

$middlewares = [

    Middleware::Cors($settings)                 //(optional) instance of Neomerx\Cors\Contracts\Strategies\SettingsStrategyInterface
        ->origin('http://example.com:123')      //(optional) the server origin
        ->allowedOrigins([                      //(optional) Allowed origins
            'http://good.example.com:321' => true,
            'http://evil.example.com:123' => null,
        ])
        ->allowedMethods([                      //(optional) Allowed methods. The second argument forces to add the allowed methods to preflight response
            'GET' => true,
            'PATCH' => null,
            'POST' => true,
            'PUT' => null,
            'DELETE' => true,
        ], true)
        ->allowedHeaders([                      //(optional) Allowed headers. The second argument forces to add the allowed headers to preflight response
            'content-type' => true,
            'some-disabled-header' => null,
            'x-enabled-custom-header' => true,
        ], true)
        ->exposedHeaders([                      //(optional) Headers other than the simple ones that might be exposed to user agent
            'Content-Type' => true,
            'X-Custom-Header' => true,
            'X-Disabled-Header' => null,
        ])
        ->allowCredentials()                    //(optional) If access with credentials is supported by the resource.
        ->maxAge(0)                             //(optional) Set pre-flight cache max period in seconds.
        ->checkHost(true)                       //(optional) If request 'Host' header should be checked against server's origin.
];

Csp

使用 paragonie/csp-builder 库在响应中添加 Content-Security-Policy 头。

$middlewares = [

    Middleware::csp($directives)                          //(optional) the array with the directives.
        ->addSource('img-src', 'https://ytimg.com')       //(optional) to add extra sources to whitelist
        ->addDirective('upgrade-insecure-requests', true) //(optional) to add new directives (if it doesn't already exist)
        ->supportOldBrowsers(false)                       //(optional) support old browsers (e.g. safari). True by default
];

Csrf

为了防止 CSRF(跨站请求伪造),中间件会在所有 POST 表单中注入一个带令牌的隐藏输入,然后检查该令牌是否有效。使用 ->autoInsert() 自动插入令牌,或者如果您更愿意,可以使用生成器调用。

$middlewares = [

    //required to save the tokens in the user session
    Middleware::AuraSession(),
    //or
    Middleware::PhpSession(),

    //required to get the format of the request (only executed in html requests)
    Middleware::FormatNegotiator(),

    //required to get the user ip
    Middleware::ClientIp(),

    Middleware::Csrf()
        ->autoInsert(), //(optional) To insert automatically the tokens in all POST forms

    function ($request, $response, $next) {
        //Get a callable to generate tokens (only if autoInsert() is disabled)
        $generator = Middleware\Csrf::getGenerator($request);

        //Use the generator (you must pass the action url)
        $response->getBody()->write(
            '<form action="/action.php" method="POST">'.
            $generator('/action.php').
            '<input type="submit">'.
            '</form>'
        );

        return $next($request, $response);
    }
];

DebugBar

PHP debug bar 1.x 插入 html 主体中。此中间件要求在插入调试栏之前执行 Middleware::formatNegotiator,以便只在 Html 响应中插入调试栏。

use Psr7Middlewares\Middleware;
use DebugBar\StandardDebugBar;

$debugBar = new StandardDebugBar();

$middlewares = [

    Middleware::FormatNegotiator(), //(recomended) to insert only in html responses

    Middleware::DebugBar($debugBar) //(optional) Instance of debugbar
        ->captureAjax(true)         //(optional) To send data in headers in ajax
];

Delay

延迟响应以模拟本地环境中的慢带宽。您可以使用一个数字或一个数组来生成秒的随机值。

use Psr7Middlewares\Middleware;

$middlewares = [

    Middleware::delay(3.5),      //delay the response 3.5 seconds

    Middleware::delay([1, 2.5]), //delay the response between 1 and 1.5 seconds
];

DetectDevice

使用 Mobile-Detect 库检测客户端设备。

use Psr7Middlewares\Middleware;
use Psr7Middlewares\Middleware\DetectDevice;

$middlewares = [

    Middleware::DetectDevice(),

    function ($request, $response, $next) {
        //Get the device info
        $device = DetectDevice::getDevice($request);

        if ($device->isMobile()) {
            //mobile stuff
        }
        elseif ($device->isTablet()) {
            //tablet stuff
        }
        elseif ($device->is('bot')) {
            //bot stuff
        }

        return $next($request, $response);
    },
];

DigestAuthentication

实现 digest http 认证。您必须提供一个包含用户和密码的数组。

use Psr7Middlewares\Middleware;

$middlewares = [

    Middleware::DigestAuthentication([
            'username1' => 'password1',
            'username2' => 'password2'
        ])
        ->realm('My realm') //(optional) custom realm value
        ->nonce(uniqid()),   //(optional) custom nonce value

    function ($request, $response, $next) {
        $username = DigestAuthentication::getUsername($request);

        return $next($request, $response);
    }
];

EncodingNegotiator

使用 willdurand/Negotiation (2.x) 检测和协商文档的编码类型。

use Psr7Middlewares\Middleware;
use Psr7Middlewares\Middleware\EncodingNegotiator;

$middlewares = [

    Middleware::EncodingNegotiator()
        ->encodings(['gzip', 'deflate']), //(optional) configure the supported encoding types

    function ($request, $response, $next) {
        //get the encoding (for example: gzip)
        $encoding = EncodingNegotiator::getEncoding($request);

        return $next($request, $response);
    }
];

ErrorHandler

如果下一个中间件返回的响应有错误(状态码 400-599),则执行处理程序。您还可以捕获抛出的异常。

use Psr7Middlewares\Middleware;
use Psr7Middlewares\Middleware\ErrorHandler;

function handler($request, $response, $myApp) {
    switch ($response->getStatusCode()) {
        case 404:
            return 'Page not found';

        case 500:
            //you can get the exception catched
            $exception = ErrorHandler::getException($request);

            return 'Server error: '.$exception->getMessage();

        default:
            return 'There was an error'
    }
}

$middlewares = [

    Middleware::ErrorHandler('handler') //(optional) The error handler
        ->arguments($myApp)             //(optional) extra arguments to the handler
        ->catchExceptions()             //(optional) to catch exceptions
        ->statusCode(function ($code) { //(optional) configure which status codes you want to handle with the errorHandler
            return $code >= 400 && $code < 600 && $code != 422; // you can bypass some status codes in case you want to handle it
        })
];

Expires

在响应中添加 Expiresmax-age 指令的 Cache-Control 头。它与 Apache 模块 mod_expires 类似。默认使用与 h5bp apache 配置 相同的配置。对于静态文件很有用。

use Psr7Middlewares\Middleware;

$middlewares = [

    Middleware::formatNegotiator(), //(recomended) to detect the content-type header

    Middleware::expires()
        ->addExpire('text/css', '+1 week') //Add or edit the expire of some types
];

FastRoute

作为中间件使用 FastRoute

use Psr7Middlewares\Middleware;

$router = FastRoute\simpleDispatcher(function (FastRoute\RouteCollector $r) {

    $r->addRoute('GET', '/blog/{id:[0-9]+}', function ($request, $response, $app) {
        return 'This is the post number'.$request->getAttribute('id');
    });
});

$middlewares = [

    Middleware::FastRoute($router) //Instance of FastRoute\Dispatcher
        ->argument($myApp)         //(optional) arguments appended to the controller
];

Firewall

使用 M6Web/Firewall 提供 IP 过滤。此中间件依赖于 ClientIp(从头中提取 IP)。

查看 允许的 IP 格式,以确定受信任/不受信任选项。

use Psr7Middlewares\Middleware;

$middlewares = [

    //required to capture the user ips before
    Middleware::ClientIp(),

    //set the firewall
    Middleware::Firewall()
        ->trusted(['123.0.0.*'])   //(optional) ips allowed
        ->untrusted(['123.0.0.1']) //(optional) ips not allowed
];

FormatNegotiator

使用 willdurand/Negotiation (2.x) 检测和协商文档的格式,使用 URL 扩展名和/或 Accept http 头。它还会在响应中添加 Content-Type 头,如果它缺失的话。

use Psr7Middlewares\Middleware;
use Psr7Middlewares\Middleware\FormatNegotiator;

$middlewares = [

    Middleware::FormatNegotiator()
        ->defaultFormat('html') //(optional) default format if it's unable to detect. (by default is "html")
        ->addFormat('tiff', ['image/tiff', 'image/x-tiff']), //(optional) add a new format associated with mimetypes

    function ($request, $response, $next) {
        //get the format (for example: html)
        $format = FormatNegotiator::getFormat($request);

        return $next($request, $response);
    }
];

您可以指定服务器支持的格式,以优先级顺序,第一个元素是默认值。

//This will only negotiate html, pdf and xml. html is the default.
Middleware::FormatNegotiator([
    'html' => [['html', 'htm', 'php'], ['text/html', 'application/xhtml+xml']],
    'pdf' => [['pdf'], ['application/pdf', 'application/x-download']],
    'xml' => [['xml'], ['text/xml', 'application/xml', 'application/x-xml']]
])

如果客户端请求的格式不被服务器支持,则将使用默认格式。如果您希望生成 406 Not Acceptable 响应,则将默认格式设置为 null。

//This will generate a 406 Not Acceptable response if the client requests anything other than html.
Middleware::FormatNegotiator([
        'html' => [['html', 'htm', 'php'], ['text/html', 'application/xhtml+xml']]
    ])
    ->defaultFormat(null)

FormTimestamp

简单的基于在所有 POST 表单中注入当前时间戳的隐藏输入的垃圾邮件保护。在提交表单时,检查时间值。如果它少于(例如)3 秒前,则假设它是机器人,因此返回 403 响应。您还可以设置表单过期前的最大秒数。

use Psr7Middlewares\Middleware;

$middlewares = [

    //(recomended) to detect html responses
    Middleware::FormatNegotiator(),

    Middleware::FormTimestamp()
        ->key('my-secret-key'),   //Key used to encrypt/decrypt the input value.
        ->min(5)                  //(optional) Minimum seconds needed to validate the request (default: 3)
        ->max(3600)               //(optional) Life of the form in second. Default is 0 (no limit)
        ->inputName('time-token') //(optional) Name of the input (default: hpt_time)
        ->autoInsert(),           //(optional) To insert automatically the inputs in all POST forms

    function ($request, $response, $next) {
        //Get a callable to generate the inputs (only if autoInsert() is disabled)
        $generator = Middleware\FormTimestamp::getGenerator($request);

        //Use the generator (you must pass the action url)
        $response->getBody()->write(
            '<form action="/action.php" method="POST">'.
            $generator().
            '<input type="submit">'.
            '</form>'
        );

        return $next($request, $response);
    }
];

Geolocate

使用 Geocoder 库 使用 IP 定位客户端。此中间件依赖于 ClientIp(从头中提取 IP)。

use Psr7Middlewares\Middleware;
use Psr7Middlewares\Middleware\Geolocate;

$middlewares = [

    //(optional) only if you want to save the result in the user session
    Middleware::PhpSession(),
    //or
    Middleware::AuraSession(),


    //required to capture the user ips before
    Middleware::ClientIp(),

    Middleware::Geolocate($geocoder) //(optional) To provide a custom Geocoder instance
        ->saveInSession(),           //(optional) To save the result to reuse in the future requests (required a session middleware before)

    function ($request, $response, $next) {
        //get the location
        $addresses = Geolocate::getLocation($request);

        //get the country
        $country = $addresses->first()->getCountry();

        $response->getBody()->write('Hello to '.$country);

        return $next($request, $response);
    }
];

GoogleAnalytics

在所有 html 页面中注入 Google Analytics 代码。

use Psr7Middlewares\Middleware;

$middlewares = [

    //(recomended) to detect html responses
    Middleware::formatNegotiator(),

    Middleware::GoogleAnalytics('UA-XXXXX-X') //The site id
];

Gzip

使用 gzip 函数压缩响应体,同时也会插入 Content-Encoding 头。

use Psr7Middlewares\Middleware;

$middlewares = [

    //required to get the preferred encoding type
    Middleware::EncodingNegotiator(),

    Middleware::Gzip()
];

Honeypot

实现了蜜罐垃圾邮件预防功能。该技术基于创建一个应隐藏且由真实用户留空、但大多数垃圾邮件机器人会填写的输入字段。中间件扫描HTML代码,并将这些输入插入所有表单中,并在接收到的请求中检查该值是否存在且为空(真实用户)或不存在或有值(机器人),返回403响应。

use Psr7Middlewares\Middleware;

$middlewares = [

    //(recomended) to detect html responses
    Middleware::formatNegotiator(),

    Middleware::Honeypot()
        ->inputName('my_name') //(optional) The name of the input field (by default "hpt_name")
        ->inputClass('hidden') //(optional) The class of the input field (by default "hpt_input")
        ->autoInsert(),        //(optional) To insert automatically the inputs in all POST forms

    function ($request, $response, $next) {
        //Get a callable to generate the inputs (only if autoInsert() is disabled)
        $generator = Middleware\Honeypot::getGenerator($request);

        //Use the generator (you must pass the action url)
        $response->getBody()->write(
            '<form action="/action.php" method="POST">'.
            $generator().
            '<input type="submit">'.
            '</form>'
        );

        return $next($request, $response);
    }
];

Https

如果请求URI是http,则返回重定向到https方案。它还添加了严格传输安全头部,以防止协议降级攻击和cookie劫持。

use Psr7Middlewares\Middleware;

$middlewares = [

    Middleware::Https(true)   //(optional) True to force https, false to force http (true by default)
        ->maxAge(1000000)     //(optional) max-age directive for the Strict-Transport-Security header. By default is 31536000 (1 year)
        ->includeSubdomains() //(optional) To add the "includeSubDomains" attribute to the Strict-Transport-Security header.
];

ImageTransformer

使用imagecow/imagecow 2.x按需转换图像。您可以调整大小、裁剪、旋转并转换为其他格式。使用imagecow语法来定义可用的尺寸。

要定义可用的尺寸,您需要分配一个表示尺寸的文件名前缀,因此任何带有此前缀的请求文件都将动态转换。

还支持客户端提示,以避免提供比所需更大的图像(目前仅在chrome和opera中支持)。

如果您想将转换后的图像保存到缓存中,请提供与psr-6兼容的库。

use Psr7Middlewares\Middleware;

$middlewares = [

    //(recomended) to detect responses' mimetype
    Middleware::formatNegotiator(),

    Middleware::imageTransformer([   // The available sizes of the images.
            'small.' => 'resizeCrop,50,50', //Creates a 50x50 thumb of any image prefixed with "small." (example: /images/small.avatar.jpg)
            'medium.' => 'resize,500|format,jpg', //Resize the image to 500px and convert to jpg
            'pictures/large.' => 'resize,1000|format,jpg', //Transform only images inside "pictures" directory (example: /images/pcitures/large.avatar.jpg)
        ])
        ->clientHints()              // (optional) To enable the client hints headers
        ->cache(new Psr6CachePool()) // (optional) To save the transformed images in the cache

    function ($request, $response, $next) {
        //Get the generator to generate urls
        $generator = Middleware\ImageTransformer::getGenerator($request);

        //Use the generator
        $response->getBody()->write('<img src="'.$generator('images/picture.jpg', 'small.').'">');

        return $next($request, $response);
    }
];

IncludeResponse

对包括旧式应用程序很有用,其中每个页面都有自己的PHP文件。例如,假设我们有一个路径如/about-us.php/about-us(解析为/about-us/index.php)的应用程序,这个中间件获取PHP文件,安全地包含它,捕获发送的输出和头部,并使用结果创建响应。如果文件不存在,则返回404响应(除非continueOnError为true)。

use Psr7Middlewares\Middleware;

$middlewares = [
    Middleware::includeResponse('/doc/root'), //The path of the document root
        ->continueOnError(true)               // (optional) to continue with the next middleware on error or not
];

JsonValidator

使用justinrainbow/json-schema使用JSON模式验证application/json请求体

use Psr7Middlewares\Middleware;
use Psr7Middlewares\Middleware\JsonValidator;

// Validate using a file:
$middlewares = [
    Middleware::payload(['forceArray' => false]),
    JsonValidator::fromFile(new \SplFileObject(WEB_ROOT . '/json-schema/en.v1.users.json')),
];

// Validate using an array:
$middlewares = [
    Middleware::payload(['forceArray' => false]),
    JsonValidator::fromArray([
        '$schema' => 'https://json-schema.fullstack.org.cn/draft-04/schema#',
        'type' => 'object',
        'properties' => [
            'id' => [
                'type' => 'string'
            ],
        ],
        'required' => [
            'id',
        ]
    ]);
];

// Override the default error handler, which responds with a 422 status code and application/json Content-Type:
$middlewares = [
    Middleware::payload(['forceArray' => false]),
    JsonValidator::fromFile(new \SplFileObject('schema.json'))
        ->setErrorHandler(function ($request, $response, array $errors) {
            $response->getBody()->write('Failed JSON validation.');

            return $response->withStatus(400, 'Oops')
                ->withHeader('Content-Type', 'text/plain');
        }),
];

JsonSchema

使用justinrainbow/json-schema使用路由匹配的JSON模式验证application/json请求体

use Psr7Middlewares\Middleware;

$middlewares = [

    // Transform `application/json` into an object, which is a requirement of `justinrainbow/json-schema`.
    Middleware::payload([
        'forceArray' => false,
    ]),

    // Provide a map of route-prefixes to JSON schema files.
    Middleware::jsonSchema([
        '/en/v1/users' => WEB_ROOT . '/json-schema/en.v1.users.json',
        '/en/v1/posts' => WEB_ROOT . '/json-schema/en.v1.posts.json',
        '/en/v2/posts' => WEB_ROOT . '/json-schema/en.v2.posts.json',
    ])
];

LanguageNegotiation

使用willdurand/Negotiation检测和协商客户端语言,使用Accept-Language头部和(可选)uri的路径。您必须提供一个包含所有可用语言的数组

use Psr7Middlewares\Middleware;
use Psr7Middlewares\Middleware\LanguageNegotiator;

$middlewares = [

    Middleware::LanguageNegotiator(['gl', 'en']) //Available languages
        ->usePath(true)                          //(optional) To search the language in the path: /gl/, /en/
        ->redirect()                             //(optional) To return a redirection if the language is not in the path

    function ($request, $response, $next) {
        //Get the preferred language
        $language = LanguageNegotiator::getLanguage($request);

        return $next($request, $response);
    }
];

LeagueRoute

league/route (2.x)作为中间件使用

use Psr7Middlewares\Middleware;
use League\Route\RouteCollection;

$router = new RouteCollection();

$router->get('/blog/{id:[0-9]+}', function ($request, $response, $vars) {
    return 'This is the post number'.$vars['id'];
});

$middlewares = [

    Middleware::LeagueRoute($router) //The RouteCollection instance
];

MethodOverride

使用X-Http-Method-Override头部覆盖请求方法。这对于无法发送除GET和POST之外的其他方法的客户端很有用

use Psr7Middlewares\Middleware;

$middlewares = [

    Middleware::MethodOverride()
        ->get(['HEAD', 'CONNECT', 'TRACE', 'OPTIONS']), //(optional) to customize the allowed GET overrided methods
        ->post(['PATCH', 'PUT', 'DELETE', 'COPY', 'LOCK', 'UNLOCK']), //(optional) to customize the allowed POST overrided methods
        ->parameter('method-override') //(optional) to use a parsed body and uri query parameter in addition to the header
        ->parameter('method-override', false) //(optional) to use only the parsed body (but not the uri query)
];

Minify

使用mrclay/minify对响应中的html、css和js代码进行压缩。

use Psr7Middlewares\Middleware;

$middlewares = [

    //(recomended) to detect the mimetype of the response
    Middleware::formatNegotiator(),

    Middleware::Minify()
];

Payload

如果未解析请求体且方法为POST、PUT或DELETE,则解析请求体。它支持json、csv和url编码格式。

use Psr7Middlewares\Middleware;

$middlewares = [

    Middleware::Payload([     // (optional) Array of parsing options:
        'forceArray' => false // Force to use arrays instead objects in json (true by default)
    ])
    ->override(),             // (optional) To override the existing parsed body if exists (false by default)

    function ($request, $response, $next) {
        //Get the parsed body
        $content = $request->getParsedBody();

        return $next($request, $response);
    }
];

PhpSession

使用请求数据初始化php会话

use Psr7Middlewares\Middleware;

$middlewares = [

    Middleware::PhpSession()
        ->name('SessionId') //(optional) Name of the session
        ->id('ABC123')      //(optional) Id of the session

    function ($request, $response, $next) {
        //Use the global $_SESSION variable to get/set data
        $_SESSION['name'] = 'John';

        return $next($request, $response);
    }
];

Piwik

要使用Piwik分析平台。在</body>关闭标签前注入javascript代码。

use Psr7Middlewares\Middleware;

$middlewares = [

    //(recomended) to detect html responses
    Middleware::formatNegotiator(),

    Middleware::Piwik()
        ->piwikUrl('//example.com/piwik')    // The url of the installed piwik
        ->siteId(1)                          // (optional) The site id (1 by default)
        ->addOption('setDoNotTrack', 'true') // (optional) Add more options to piwik API
];

ReadResponse

从文件中读取响应内容。这是SaveResponse的相反操作。选项continueOnError更改中间件的行为,以便在响应文件找不到时继续下一个中间件,并在找到文件时直接返回响应。这对于将中间件用作基于文件的缓存并将路由中间件(或其他readResponses)添加到队列中很有用。

use Psr7Middlewares\Middleware;

$middlewares = [

    Middleware::ReadResponse('path/to/files') // Path where the files are stored
        ->appendQuery(true)                   // (optional) to use the uri query in the filename
        ->continueOnError(true)               // (optional) to continue with the next middleware on error or not
];

Recaptcha

要使用google recaptcha库进行垃圾邮件预防。

use Psr7Middlewares\Middleware;

$middlewares = [

    //required to get the user IP
    Middleware::ClientIp(),

    Middleware::Recaptcha('secret') //The secret key
];

Rename

重命名请求路径。这在某些用例中很有用。

  • 出于安全原因,通过随机后缀重命名公共路径,例如将路径 /admin 重命名为更不可预测的 /admin-19640983
  • 创建没有使用任何路由器的美观网址。例如,要通过更友好的 /about-me 访问路径 /static-pages/about-me.php

请注意,原始路径将不可公开访问。在上面的例子中,对 /admin/static-pages/about-me.php 的请求将返回404响应。

use Psr7Middlewares\Middleware;

$middlewares = [

    Middleware::Rename([
        '/admin' => '/admin-19640983',
    ]),

    function ($request, $response, $next) {
        $path = $request->getUri()->getPath(); // /admin

        return $next($request, $response);
    }
];

ResponseTime

计算响应时间(以毫秒为单位)并将其保存到 X-Response-Time 标头

use Psr7Middlewares\Middleware;

$middlewares = [

    Middleware::ResponseTime()
];

Robots

在非生产环境中禁用搜索引擎的机器人。自动在所有响应中添加 X-Robots-Tag: noindex, nofollow, noarchive 标头,并返回默认的 /robots.txt 请求正文。

use Psr7Middlewares\Middleware;

$middlewares = [

    Middleware::Robots(false) //(optional) Set true to allow search engines instead disallow
];

SaveResponse

如果满足以下所有条件,则将响应内容保存到文件中

  • 方法是 GET
  • 状态码是 200
  • Cache-Control 标头不包含 no-cache
  • 请求没有查询参数。

这对于缓存很有用。

use Psr7Middlewares\Middleware;

$middlewares = [

    Middleware::SaveResponse('path/to/files') //Path directory where save the responses
        ->appendQuery(true)                   // (optional) to append the uri query to the filename
];

Shutdown

用于显示503维护页面。您需要指定一个处理器。

use Psr7Middlewares\Middleware;

function shutdownHandler ($request, $response, $app) {
    $response->getBody()->write('Service unavailable');
}

$middlewares = [

    Middleware::Shutdown('shutdownHandler') //(optional) Callable that generate the response
        ->arguments($app)                   //(optional) to add extra arguments to the handler
];

TrailingSlash

删除(或添加)路径的尾部斜杠。例如,/post/23/ 将被转换为 /post/23。如果路径是 /,则不会进行转换。如果您在路由器方面有问题,这很有用。

use Psr7Middlewares\Middleware;

$middlewares = [

    Middleware::TrailingSlash(true) //(optional) set true to add the trailing slash instead remove
        ->redirect(301)             //(optional) to return a 301 (seo friendly) or 302 response to the new path
];

Uuid

使用 ramsey/uuid (3.x) 为每个请求生成 Uuid(通用唯一标识符)(兼容 RFC 4122 版本 1、3、4 和 5)。这对于调试很有用。

use Psr7Middlewares\Middleware;
use Psr7Middlewares\Middleware\Uuid;

$middlewares = [

    Middleware::Uuid()
        ->version(4)     //(optional) version of the identifier (1 by default). Versions 3 and 5 need more arguments (see https://github.com/ramsey/uuid#examples)
        ->header(false), //(optional) Name of the header to store the identifier (X-Uuid by default). Set false to don't save header

    function ($request, $response, $next) {
        //Get the X-Uuid header
        $id = $request->getHeaderLine('X-Uuid');

        //Get the Uuid instance
        $uuid = Uuid::getUuid($request);

        echo $uuid->toString();

        return $next($request, $response);
    }
];

Whoops

whoops 2.x 用作错误处理器。

use Psr7Middlewares\Middleware;
use Psr7Middlewares\Middleware\Whoops;
use Whoops\Run;

$whoops = new Run();

$middlewares = [

    //(recomended) to allows to choose the best handler according with the response mimetype
    Middleware::formatNegotiator(),

    Middleware::Whoops($whoops) //(optional) provide a custom whoops instance
        ->catchErrors(false)    //(optional) to catch not only exceptions but also php errors (true by default)
];

Www

在主机 uri 中添加或删除 www 子域名,并可选地返回重定向响应。以下类型的宿主值将不会更改

  • 单词主机,例如:https://
  • 基于 IP 的主机,例如:http://0.0.0.0
  • 多域名主机,例如:http://subdomain.example.com
use Psr7Middlewares\Middleware;

$middlewares = [

    Middleware::Www(true) //(optional) Add www instead remove it
        ->redirect(301)   //(optional) to return a 301 (seo friendly), 302 response to the new host or false to don't redirect. (301 by default)
];

懒/条件式中间件创建

在某些情况下,您可能希望以懒的方式创建中间件。

  • 中间件仅在特定上下文中需要(例如在开发环境中)
  • 中间件创建成本很高,并不总是需要(因为之前的中间件返回缓存的响应)
  • 中间件仅在特定路径中需要

要处理这种情况,您可以使用 Middleware::create() 方法,该方法必须返回一个可调用对象或 false。例如

use Psr7Middlewares\Middleware;

$middlewares = [

    //This middleware can return a cached response
    //so the next middleware may not be executed
    Middleware::cache($myPsr6CachePool),

    //Let's say this middleware is expensive, so use a proxy for lazy creation
    Middleware::create(function () use ($app) {
        return Middleware::auraRouter($app->get('router'));
    }),

    //This middleware is needed only in production
    Middleware::create(function () {
        return (getenv('ENV') !== 'production') ? false : Middleware::minify();
    }),

    //This middleware is needed in some cases
    Middleware::create(function ($request, $response) {
        if ($request->hasHeader('Foo')) {
            return Middleware::www();
        }

        return false;
    }),

    //This middleware is needed only in a specific basePath
    Middleware::create('/admin', function () {
        return Middleware::DigestAuthentication(['user' => 'pass']);
    }),

    //This middleware is needed in some cases under a specific basePath
    Middleware::create('/actions', function ($request, $response) {
        if ($request->hasHeader('Foo')) {
            return Middleware::responseTime();
        }

        return false;
    }),
];

扩展中间件

某些中间件部分根据某些情况使用不同的函数来更改 HTTP 消息。例如,Payload 解析原始正文内容,使用的方法取决于内容的类型:可以是 json、urlencoded、csv 等。另一个例子是 Minify 中间件,它需要每个格式(html、css、js 等)的不同压缩器,或者 Gzip,它根据 Accept-Encoding 标头使用不同的方法压缩响应正文。

Psr7Middlewares\Transformers\ResolverInterface 接口提供了一种在每个情况下解析和返回适当的“转换器”的方法。转换器只是一个具有特定签名的可调用对象。您可以根据自己的需要创建自定义解析器或扩展此包中包含的解析器。让我们看一个例子

use Psr7Middlewares\Middleware;
use Psr7Middlewares\Transformers\BodyParser;
use Psr\Http\Message\ServerRequestInterface;

class MyBodyParser extends BodyParser
{
    /**
     * New parser used in request with the format "php"
     */
    public function php(ServerRequestInterface $request)
    {
        $data = unserialize((string) $request->getBody());

        return $request->withParsedBody($data);
    }
}

//Use the resolver
$middlewares = [
    Middleware::Payload()->resolver(new MyBodyParser())
];

以下中间件正在使用您可以根据自己的需要进行自定义的解析器

  • Payload 根据格式(json、urlencoded、csv 等)解析正文。
  • Gzip 使用浏览器支持的编码方法(gzip,deflate)对内容进行编码
  • Minify 为每种格式使用不同的压缩器(html,css,js,...)

贡献

欢迎提交新的中间件。只需创建一个pull request即可。