Laravel 原始碼閱讀指南 -- HTTP 核心

KevinYan發表於2018-11-10

Http Kernel是Laravel中用來串聯框架的各個核心元件來網路請求的,簡單的說只要是通過public/index.php來啟動框架的都會用到Http Kernel,而另外的類似通過artisan命令、計劃任務、佇列啟動框架進行處理的都會用到Console Kernel, 今天我們先梳理一下Http Kernel做的事情。

核心繫結

既然Http Kernel是Laravel中用來串聯框架的各個部分處理網路請求的,我們來看一下核心是怎麼載入到Laravel中應用例項中來的,在public/index.php中我們就會看見首先就會通過bootstrap/app.php這個腳手架檔案來初始化應用程式:

下面是 bootstrap/app.php 的程式碼,包含兩個主要部分建立應用例項繫結核心至 APP 服務容器

<?php
// 第一部分: 建立應用例項
$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);

// 第二部分: 完成核心繫結
$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

return $app;

HTTP 核心繼承自 Illuminate\Foundation\Http\Kernel類,在 HTTP 核心中 內它定義了中介軟體相關陣列, 中介軟體提供了一種方便的機制來過濾進入應用的 HTTP 請求和加工流出應用的HTTP響應。

<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        \App\Http\Middleware\TrustProxies::class,
    ];
    /**
     * The application's route middleware groups.
     *
     * @var array
     */
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
        'api' => [
            'throttle:60,1',
            'bindings',
        ],
    ];
    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    ];
}

在其父類 「Illuminate\Foundation\Http\Kernel」 內部定義了屬性名為 「bootstrappers」 的 載入程式 陣列:

protected $bootstrappers = [
    \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
    \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
    \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
    \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
    \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
    \Illuminate\Foundation\Bootstrap\BootProviders::class,
];

載入程式組中 包括完成環境檢測、配置載入、異常處理、Facades 註冊、服務提供者註冊、啟動服務這六個載入程式。

有關中介軟體和載入程式相關內容的講解可以瀏覽我們之前相關章節的內容。

應用解析核心

在將應用初始化階段將Http核心繫結至應用的服務容器後,緊接著在public/index.php中我們可以看到使用了服務容器的make方法將Http核心例項解析了出來:

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

在例項化核心時,將在 HTTP 核心中定義的中介軟體註冊到了 路由器,註冊完後就可以在實際處理 HTTP 請求前呼叫路由上應用的中介軟體實現過濾請求的目的:

namespace Illuminate\Foundation\Http;
...
class Kernel implements KernelContract
{
    /**
     * Create a new HTTP kernel instance.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @param  \Illuminate\Routing\Router  $router
     * @return void
     */
    public function __construct(Application $app, Router $router)
    {
        $this->app = $app;
        $this->router = $router;

        $router->middlewarePriority = $this->middlewarePriority;

        foreach ($this->middlewareGroups as $key => $middleware) {
            $router->middlewareGroup($key, $middleware);
        }

        foreach ($this->routeMiddleware as $key => $middleware) {
            $router->aliasMiddleware($key, $middleware);
        }
    }
}

namespace Illuminate/Routing;
class Router implements RegistrarContract, BindingRegistrar
{
    /**
     * Register a group of middleware.
     *
     * @param  string  $name
     * @param  array  $middleware
     * @return $this
     */
    public function middlewareGroup($name, array $middleware)
    {
        $this->middlewareGroups[$name] = $middleware;

        return $this;
    }

    /**
     * Register a short-hand name for a middleware.
     *
     * @param  string  $name
     * @param  string  $class
     * @return $this
     */
    public function aliasMiddleware($name, $class)
    {
        $this->middleware[$name] = $class;

        return $this;
    }
}

處理HTTP請求

通過服務解析完成Http核心例項的建立後就可以用HTTP核心例項來處理HTTP請求了

//public/index.php
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

在處理請求之前會先通過Illuminate\Http\Requestcapture() 方法以進入應用的HTTP請求的資訊為基礎建立出一個 Laravel Request請求例項,在後續應用剩餘的生命週期中Request請求例項就是對本次HTTP請求的抽象,關於Laravel Request請求例項的講解可以參考以前的章節。

將HTTP請求抽象成Laravel Request請求例項後,請求例項會被傳導進入到HTTP核心的handle方法內部,請求的處理就是由handle方法來完成的。

namespace Illuminate\Foundation\Http;

class Kernel implements KernelContract
{
    /**
     * Handle an incoming HTTP request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function handle($request)
    {
        try {
            $request->enableHttpMethodParameterOverride();

            $response = $this->sendRequestThroughRouter($request);
        } catch (Exception $e) {
            $this->reportException($e);

            $response = $this->renderException($request, $e);
        } catch (Throwable $e) {
            $this->reportException($e = new FatalThrowableError($e));

            $response = $this->renderException($request, $e);
        }

        $this->app['events']->dispatch(
            new Events\RequestHandled($request, $response)
        );

        return $response;
    }
}

handle 方法接收一個請求物件,並最終生成一個響應物件。其實handle方法我們已經很熟悉了在講解很多模組的時候都是以它為出發點逐步深入到模組的內部去講解模組內的邏輯的,其中sendRequestThroughRouter方法在服務提供者和中介軟體都提到過,它會載入在核心中定義的載入程式來引導啟動應用然後會將使用Pipeline物件傳輸HTTP請求物件流經框架中定義的HTTP中介軟體們和路由中介軟體們來完成過濾請求最終將請求傳遞給處理程式(控制器方法或者路由中的閉包)由處理程式返回相應的響應。關於handle方法的註解我直接引用以前章節的講解放在這裡,具體更詳細的分析具體是如何引導啟動應用以及如何將傳輸流經各個中介軟體併到達處理程式的內容請檢視服務提供器中介軟體還有路由這三個章節。

protected function sendRequestThroughRouter($request)
{
    $this->app->instance('request', $request);

    Facade::clearResolvedInstance('request');

    $this->bootstrap();

    return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
}

/*引導啟動Laravel應用程式
1. DetectEnvironment  檢查環境
2. LoadConfiguration  載入應用配置
3. ConfigureLogging   配置日至
4. HandleException    註冊異常處理的Handler
5. RegisterFacades    註冊Facades 
6. RegisterProviders  註冊Providers 
7. BootProviders      啟動Providers
*/
public function bootstrap()
{
    if (! $this->app->hasBeenBootstrapped()) {
    /**依次執行$bootstrappers中每一個bootstrapper的bootstrap()函式
        $bootstrappers = [
             'Illuminate\Foundation\Bootstrap\DetectEnvironment',
             'Illuminate\Foundation\Bootstrap\LoadConfiguration',
             'Illuminate\Foundation\Bootstrap\ConfigureLogging',
             'Illuminate\Foundation\Bootstrap\HandleExceptions',
             'Illuminate\Foundation\Bootstrap\RegisterFacades',
             'Illuminate\Foundation\Bootstrap\RegisterProviders',
             'Illuminate\Foundation\Bootstrap\BootProviders',
            ];*/
            $this->app->bootstrapWith($this->bootstrappers());
    }
}

傳送響應

經過上面的幾個階段後我們最終拿到了要返回的響應,接下來就是傳送響應了。

//public/index.php
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

// 傳送響應
$response->send();

傳送響應由 Illuminate\Http\Responsesend()方法完成父類其定義在父類Symfony\Component\HttpFoundation\Response中。

public function send()
{
    $this->sendHeaders();// 傳送響應頭部資訊
    $this->sendContent();// 傳送報文主題

    if (function_exists('fastcgi_finish_request')) {
        fastcgi_finish_request();
    } elseif (!\in_array(PHP_SAPI, array('cli', 'phpdbg'), true)) {
        static::closeOutputBuffers(0, true);
    }
    return $this;
}

關於Response物件的詳細分析可以參看我們之前講解Laravel Response物件的章節。

終止應用程式

響應傳送後,HTTP核心會呼叫terminable中介軟體做一些後續的處理工作。比如,Laravel 內建的「session」中介軟體會在響應傳送到瀏覽器之後將會話資料寫入儲存器中。

// public/index.php
// 終止程式
$kernel->terminate($request, $response);
//Illuminate\Foundation\Http\Kernel
public function terminate($request, $response)
{
    $this->terminateMiddleware($request, $response);
    $this->app->terminate();
}

// 終止中介軟體
protected function terminateMiddleware($request, $response)
{
    $middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge(
        $this->gatherRouteMiddleware($request),
        $this->middleware
    );
    foreach ($middlewares as $middleware) {
        if (! is_string($middleware)) {
            continue;
        }
        list($name, $parameters) = $this->parseMiddleware($middleware);
        $instance = $this->app->make($name);
        if (method_exists($instance, 'terminate')) {
            $instance->terminate($request, $response);
        }
    }
}

Http核心的terminate方法會呼叫teminable中介軟體的terminate方法,呼叫完成後從HTTP請求進來到返回響應整個應用程式的生命週期就結束了。

總結

本節介紹的HTTP核心起到的主要是串聯作用,其中設計到的初始化應用、引導應用、將HTTP請求抽象成Request物件、傳遞Request物件通過中介軟體到達處理程式生成響應以及響應傳送給客戶端。這些東西在之前的章節裡都有講過,並沒有什麼新的東西,希望通過這篇文章能讓大家把之前文章裡講到的每個點串成一條線,這樣對Laravel整體是怎麼工作的會有更清晰的概念。

本文已經整理髮布到系列文章Laravel核心程式碼學習中,歡迎訪問閱讀,多多交流。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

公眾號:網管叨bi叨 | Golang、PHP、Laravel、Docker等學習經驗分享

相關文章