欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Laravel5.5源码详解 -- Request是如何生成的?

程序员文章站 2022-05-19 15:50:14
...

Laravel5.5源码详解 – Request是如何生成的?

在laravel的启动页面,也就是public/index.php文件内,有这么一句,

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

它根据浏览器传入的HTTP请求,创建了一个Illuminate\Http\Request实例。下面,我们看一下这个实例的产生过程。

Laravel5.5源码详解 -- Request是如何生成的?

$request Illuminate\Http\Request::capture()

public static function capture()
{
    static::enableHttpMethodParameterOverride();
    return static::createFromBase(SymfonyRequest::createFromGlobals());
}

这里面涉及到三个函数,

A. Request::capture()调用的第1个和第2个函数

创建Request的时候,调用的相关函数如下

Vendor\Symfony\Http-foundation\Request

public static function enableHttpMethodParameterOverride()
{
    self::$httpMethodParameterOverride = true;
}

public static function createFromGlobals()
{
    // With the php's bug #66606, the php's built-in web server
    // stores the Content-Type and Content-Length header values in
    // HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH fields.
    // 上面官方的注释已经说明,应该有HTTP_CONTENT_TYPE 和 HTTP_CONTENT_LENGTH,但调试发现都没有!
    $server = $_SERVER;
    if ('cli-server' === PHP_SAPI) {
        if (array_key_exists('HTTP_CONTENT_LENGTH', $_SERVER)) {
            $server['CONTENT_LENGTH'] = $_SERVER['HTTP_CONTENT_LENGTH'];
        }
        if (array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) {
            $server['CONTENT_TYPE'] = $_SERVER['HTTP_CONTENT_TYPE'];
        }
    }

    // 生成Request实例
    $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $server);

    if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
        && in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH'))
    ) {
        parse_str($request->getContent(), $data);
        $request->request = new ParameterBag($data);
    }

    return $request;
}

private static function createRequestFromFactory(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
{
    if (self::$requestFactory) {
        $request = call_user_func(self::$requestFactory, $query, $request, $attributes, $cookies, $files, $server, $content);

        if (!$request instanceof self) {
            throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.');
        }

        return $request;
    }

    return new static($query, $request, $attributes, $cookies, $files, $server, $content);
}

最后这一句 return new static($query, $request, $attributes, $cookies, $files, $server, $content);,生成了Request的实例,即Symfony\Component\HttpFoundation\Request

注意,Symfony\Http-foundation\Request的namespace是Symfony\Component\HttpFoundation;

说明一:
a) $_SERVER是一个数组,它记录了服务端的信息,这个在服务器启动时生效,
array:24 [▼
  "DOCUMENT_ROOT" => "D:\wamp64\www\laravel\laraveltest\public"
  "REMOTE_ADDR" => "127.0.0.1"
  "REMOTE_PORT" => "12492"
  "SERVER_SOFTWARE" => "PHP 7.1.9 Development Server"
  "SERVER_PROTOCOL" => "HTTP/1.1"
  "SERVER_NAME" => "127.0.0.1"
  "SERVER_PORT" => "8000"
  "REQUEST_URI" => "/user/image/avatarUpload"
  "REQUEST_METHOD" => "GET"
  "SCRIPT_NAME" => "/index.php"
  "SCRIPT_FILENAME" => "D:\wamp64\www\laravel\laraveltest\public\index.php"
  "PATH_INFO" => "/user/image/avatarUpload"
  "PHP_SELF" => "/index.php/user/image/avatarUpload"
  "HTTP_HOST" => "localhost:8000"
  "HTTP_CONNECTION" => "keep-alive"
  "HTTP_CACHE_CONTROL" => "max-age=0"
  "HTTP_USER_AGENT" => "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36"
  "HTTP_UPGRADE_INSECURE_REQUESTS" => "1"
  "HTTP_ACCEPT" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"
  "HTTP_ACCEPT_ENCODING" => "gzip, deflate, br"
  "HTTP_ACCEPT_LANGUAGE" => "zh-CN,zh;q=0.9"
  "HTTP_COOKIE" => "remember_web_59ba36addc2b2f9401580f014c7f58ea4e30989d=eyJpdiI6ImdJRXdTZlNBbEt3a1pkM3p0OWxMTVE9PSIsInZhbHVlIjoiejZGdnZ2QlFDY0djQUN5bXd5eGpUVHgyZFUzTkV5ZTZqRVJCVE ▶"
  "REQUEST_TIME_FLOAT" => 1514626260.1018
  "REQUEST_TIME" => 1514626260
]
b) PHP_SAPI = ‘cli-server’,

参考, http://php.net/manual/zh/features.commandline.php

PHP可以应用在终端,也可以应用在Web服务器中;应用在终端上的SAPI就叫做CLI SAPI,应用在Web服务器中的就叫做CGI SAPI,在windows下安装php的话,对应的分别是php.exe和php-cgi.exe。

在PHP中,如何得知自己使用的是哪个 SAPI?

在命令行下,运行 php -v 便能得知该 php 是 CGI 还是 CLI。请参考函数 php_sapi_name() 以及常量 PHP_SAPI

c) $_COOKIE

特别说明一下:cookie是客户端存储数据的一个东西,它的作用是,当用户登录后,后续的请求不需要再次验证身份!原因就在于浏览器会在客户的请求的头部带上这么一个cookie值,这个cookie 值标明了本次请求的用户是谁。当然,用户不注册登陆网站,这个cookie也是必须的,只不过里面没有身份信息。

所以,当你打开浏览器,输入www.mynetwork.com时,浏览器会首先发出一个cookie给服务器mynetwork.com网站的服务器,告诉服务器有人来浏览页面了,这时候,服务器必须响应并处理cookie(即:$_COOKIE)。

另个,各个浏览器的初始cookie都是不一样的,比方说IE11是空字符串,Firefox的是下面这个样子的,

array:2 [▼
  "bdshare_firstime" => "1511944517485"
  "remember_web_59ba36addc2b2f9401580 ▶" => "eyJpdiI6InFBsOUZj ▶"
]
d) 其他(传入createRequestFromFactory的)参数

目前这些参考均为空的数组[ ],如下

$_GET, $_POST, array(), $_FILES, $server
说明二

关于Cookie管理类。

cookie管理类Illuminate\Cookie\CookieJar是在Illuminate\Cookie\CookieServiceProvider注册时创建的,

<?php
namespace Illuminate\Cookie;
use Illuminate\Support\ServiceProvider;

class CookieServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton('cookie', function ($app) {
            $config = $app->make('config')->get('session');
            return (new CookieJar)->setDefaultPathAndDomain(
                $config['path'], 
                $config['domain'], 
                $config['secure'], 
                $config['same_site'] ?? null
            );
        });
    }
}

B. Request::capture()调用的第3个

public static function createFromBase(SymfonyRequest $request)
{
    // 首先确认是不是Illuminate\Http\Request实例,这里刚创建的是
    // Symfony\Component\HttpFoundation, 所以if判断为false,
    if ($request instanceof static) {
        return $request;
    }

    $content = $request->content;

    // 因为不是Illuminate\Http\Request实例,所以这里创建一个laravel处理的Request类的实例
    $request = (new static)->duplicate(
        $request->query->all(), $request->request->all(), $request->attributes->all(),
        $request->cookies->all(), $request->files->all(), $request->server->all()
    );

    $request->content = $content;

    // 有无请求参数,如果有的话,就在这里拿(刚生成是没有的,都从$_SERVER等参数那里拿)
    $request->request = $request->getInputSource();

    return $request;
}

这里很有意思,laravel用了symfony的第三方代码,直接把Symfony\Component\HttpFoundation对象中的一切参数拿过来,利用$request = (new static)->duplicate(...) 克隆的手法,生成了一个Illuminate\Http\Request。

至此,一个完整的Illuminate\Http\Request的实例,$request请求产生了。