oscarotero/psr7-middlewares

该软件包已被废弃,不再维护。作者建议使用 middlewares/* 软件包。

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

v3.21.1 2017-09-28 21:21 UTC

README

psr7-middlewares

Build Status Scrutinizer Code Quality

SensioLabsInsight

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 以 oscarotero/psr7-middlewares 的方式安装和自动加载。

$ composer require oscarotero/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 的访问日志格式(Apache's access log format)生成每个请求的访问日志。此中间件需要一个 Psr log implementation,例如 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

实现 摘要 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

将Google Analytics代码注入所有HTML页面。

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()
];

负载

如果请求体未解析且请求方法为 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
  • 不使用任何路由器创建美观的 URL。例如,要访问更友好的路径 /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 子域名,并可选地返回重定向响应。以下类型的宿主值将不会被更改

  • 单词主机,例如: http://localhost
  • 基于 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)
];

Lazy/conditional middleware creation

在某些情况下,您可能需要以懒方式创建中间件

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

要处理这种情况,您可以使用 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;
    }),
];

Extending middlewares

某些中间件组件使用不同的函数来更改 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 等)

Contribution

欢迎添加新的中间件。只需创建一个 pull request。