HttpKernel组件:HttpKernelInterface

3.4 版本
维护中的版本

在本系列第二章的总结部分,我提出了使用Symfony组件的一个重大好处:即,使用了它们的全体框架之间的可互用性。通过让框架实现HttpKernelInterface接口,让我们朝这个目标再迈进一大步:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace Symfony\Component\HttpKernel;
 
// ...
interface HttpKernelInterface
{
    /**
     * @return Response A Response instance / 返回一个响应实例
     */
    public function handle(
        Request $request,
        $type = self::MASTER_REQUEST,
        $catch = true
    );
}

HttpKernelInterface可以说是HttpKernel组件中最为重要的代码段,这绝非玩笑。(任何)框架和程序只要实现这个接口,那便(能够做到)完全可互用。另有大量强力功能免费附送。

更新你的框架,以便实现此接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// example.com/src/Framework.php
 
// ...
use Symfony\Component\HttpKernel\HttpKernelInterface;
 
class Framework implements HttpKernelInterface
{
    // ...
 
    public function handle(
        Request $request,
        $type = HttpKernelInterface::MASTER_REQUEST,
        $catch = true
    ) {
        // ...
    }
}

就算这种改变看起来很琐碎,它带给我们更多!我们先讨论其中的一个亮点:透明化的HTTP缓存支持。HttpCache类实现的是一个“用PHP写成”的全功能反向代理;它实现的是HttpKernelInterface接口并且打包了另一个HttpKernelInterface实例:

1
2
3
4
5
6
7
8
// example.com/web/front.php
$framework = new Simplex\Framework($dispatcher, $matcher, $resolver);
$framework = new HttpKernel\HttpCache\HttpCache(
    $framework,
    new HttpKernel\HttpCache\Store(__DIR__.'/../cache')
);
 
$framework->handle($request)->send();

在我们的框架中添加对HTTP缓存的支持,上面就是全部过程。这太令人惊异,不是吗?

配置缓存时,需要通过HTTP缓存头来实现。例如,要缓存一个响应10秒钟,使用Response::setTtl()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// example.com/src/Calendar/Controller/LeapYearController.php
 
// ...
public function indexAction(Request $request, $year)
{
    $leapyear = new LeapYear();
    if ($leapyear->isLeapYear($year)) {
        $response = new Response('Yep, this is a leap year!');
    } else {
        $response = new Response('Nope, this is not a leap year.');
    }
 
    $response->setTtl(10);
 
    return $response;
}

如果,你像我一样,通过模拟请求(Request::create('/is_leap_year/2012')),从命令行来运行你的框架,你可以借助“剥离字符串”(echo $response;)来轻松调试Response实例,因为它可以显示全部头信息,连同响应内容。

为了验证缓存工作是否正常,添加一个随机数到响应内容中,然后检查这个数值“每10秒才会发生改变”:

1
$response = new Response('Yep, this is a leap year! '.rand());

当部署到生产环境时,你要使用Symfony反向代理(对于共享主机[shared hosting]来说它是很好用的),或者采用更佳方案,转向一个高效反向代理,比如Varnish

使用HTTP缓存头来管理你的程序缓存,威力巨大,它能让你精细控制缓存策略(caching strategy),因为你可以使用HTTP协议中的expiration(过期)和validation(验证)模型。如果你对这些概念感到迷惑,参考Symfony中文文档的HTTP缓存章节。

Response类里包含了许多别的方法,可以让你极为轻松地配置HTTP cache。其中一个大威力的就是setCache(),它把最常用的缓存策略给抽象到了一个简单数组中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$date = date_create_from_format('Y-m-d H:i:s', '2005-10-15 10:00:00');
 
$response->setCache(array(
    'public'        => true,
    'etag'          => 'abcde',
    'last_modified' => $date,
    'max_age'       => 10,
    's_maxage'      => 10,
));
 
// it is equivalent to the following code / 等价于下面的代码
$response->setPublic();
$response->setEtag('abcde');
$response->setLastModified($date);
$response->setMaxAge(10);
$response->setSharedMaxAge(10);

当使用validation model时,isNotModified()方法允许你尽可能早地“短接(short-circuiting,短路)”响应的生成,来轻松减少响应时间。

1
2
3
4
5
6
7
8
9
$response->setETag('whatever_you_compute_as_an_etag');
 
if ($response->isNotModified($request)) {
    return $response;
}
 
$response->setContent('The computed content of the response');
 
return $response;

使用HTTP caching是极好的,但当你不能缓存整页时怎么办?你要缓存“高动态性的侧边栏”以外的所有其他内容,又该怎么办?Edge Side IncludeESI就是对治办法!不同于生成全部内容到一个地方,ESI允许你标记一个页面的局部作为子请求(sub-request call)的内容:

1
2
3
4
5
This is the content of your page
 
Is 2012 a leap year? <esi:include src="/leapyear/2012" />
 
Some other content

为了让HtppCache能够识别ESI标签,你需要传给缓存一个ESI类的实例。ESI类自动解析ESI标签,并令子请求把标签转换为相应的正确内容:

1
2
3
4
5
$framework = new HttpKernel\HttpCache\HttpCache(
    $framework,
    new HttpKernel\HttpCache\Store(__DIR__.'/../cache'),
    new HttpKernel\HttpCache\Esi()
);

要让ESI能够工作,你需要使用一个支持它的反向代理,就像Symfony所实现的。Varnish可谓不二之选,而且还开源。

当使用复杂HTTP缓存策略,以及/或者包容大量ESI标签时,要理解为什么以及什么时候“一个资源应当被/不被缓存”会变得很困难。要简化调试,你需要开启debug模式:

1
2
3
4
5
6
$framework = new HttpKernel\HttpCache\HttpCache(
    $framework,
    new HttpKernel\HttpCache\Store(__DIR__.'/../cache'),
    new HttpKernel\HttpCache\Esi(),
    array('debug' => true)
);

debug模式对每一个响应添加了X-Symfony-Cache头,描述了缓存层(cache layer)都做了些什么:

1
2
3
X-Symfony-Cache:  GET /is_leap_year/2012: stale, invalid, store
 
X-Symfony-Cache:  GET /is_leap_year/2012: fresh

HttpCache有许多功能,像是对stale-while-revalidatestale-if-error这些“被定义在RC5861”中的HTTP Cache-Control扩展的支持。

有了这个单一接口的助力,我们的框架现在受益于HttpKernel组件中的许多功能;HTTP缓存只是其中之一,但却是重要的一个,因为它可以令你的程序飞起!

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)