zoukankan      html  css  js  c++  java
  • Yii2.0源码阅读-一次请求的完整过程

    Yii2.0框架源码阅读,从请求发起,到结束的运行步骤

    其实最初阅读是从yiiwebUrlManager这个类开始看起,不断的寻找这个类中方法的调用者,最终回到了yiiwebApplication,那就从头开始看。

    1、Nginx

    nginx作为web服务器,时刻监听着80端口,等待接收用户请求,并转发给php进行处理,Yii2.0框架使用了统一的入口脚本:index.php

    所以nginx中有如下的配置:

    location / {
        try_files $uri $uri/ /index.php?$args;
    }
    

    首先nginx会检索$uri$uri/这个路径下的文件,如果没有找到,那就将请求交给index.php

    2、创建Yii Aapplication 实例

    index.php文件可以看出加载了各个文件夹下的配置项,然后new了一个application,这个构造方法是在yiiwebApplication的父类yiiaseApplicaton中,主要根据配置项初始化:

    public function __construct($config = [])
    {
        Yii::$app = $this;
    	static::setInstance($this);
    
        $this->state = self::STATE_BEGIN;
    
        $this->preInit($config);
    
        $this->registerErrorHandler($config);
    
        Component::__construct($config);
    }
    

    3、Application run

    可以看到创建实例之后调用了run方法$applicaton->run();run方法位于yiiaseApplication中,总的来说就是执行:

    • before request 处理请求前的操作
    • handle request 真正的处理这次HTTP请求
    • after request 请求处理完成之后的操作
    • send response 将响应信息发送给客户端
    public function run()
    {
    	//代码简化 try catch去掉
        $this->state = self::STATE_BEFORE_REQUEST;
        $this->trigger(self::EVENT_BEFORE_REQUEST);
    
        $this->state = self::STATE_HANDLING_REQUEST;
        $response = $this->handleRequest($this->getRequest());
        $this->state = self::STATE_AFTER_REQUEST;
        $this->trigger(self::EVENT_AFTER_REQUEST);
    
        $this->state = self::STATE_SENDING_RESPONSE;
        $response->send();
    
        $this->state = self::STATE_END;
    
        return $response->exitStatus;
    }
    
    

    4、Get Request Object

    请求的处理从 $this->handleRequest($this->getRequest());开始,首先看handleRequest的参数是getRequest的返回值,getRequest方法位于yiiwebApplication中:

    public function getRequest()
    {
        return $this->get('request');
    }
    

    get方法,根据层层的继承关系找到其位于yiiaseApplication的父类yiiaseModule的父类yiidiServiceLocator中:

    public function get($id, $throwException = true)
    {
        if (isset($this->_components[$id])) {
            return $this->_components[$id];
        }
    
        if (isset($this->_definitions[$id])) {
            $definition = $this->_definitions[$id];
            if (is_object($definition) && !$definition instanceof Closure) {
                return $this->_components[$id] = $definition;
            } else {
                return $this->_components[$id] = Yii::createObject($definition);
            }
        } elseif ($throwException) {
            throw new InvalidConfigException("Unknown component ID: $id");
        } else {
            return null;
        }
    }
    

    这里我们先不考虑$_definitions $_components这些变量初始化的位置,打印$id='request'是获取到的$definition为:

    array(2) {
      ["cookieValidationKey"]=>
      string(32) "0drrX5wQ2wZ8Hli2Ql48ss8efcE-W11m"
      ["class"]=>
      string(15) "yiiwebRequest"
    }
    

    说一下这个结果的由来,事实上这个就是我们在config/main-local.php中的配置项:

    $config = [
        'components' => [
            'request' => [
                'cookieValidationKey' => '0drrX5wQ2wZ8Hli2Ql48ss8efcE-W11m',
            ],
        ],
    ];
    

    而我们获取到信息中有class=>yiiwebRequest,这个class的添加是在new Application的时候,也就在构造方法中执行了preInit(),preInit中对核心的组件进行了初始化,添加了class信息,然后更新到了config数组中。

    //preInit
    //coreComponents 在yiiwebApplication 与 yiiaseApplication中
    foreach ($this->coreComponents() as $id => $component) {
       if (!isset($config['components'][$id])) {
            $config['components'][$id] = $component;
        } elseif (is_array($config['components'][$id]) && !isset($config['components'][$id]['class'])) {
            $config['components'][$id]['class'] = $component['class'];
        }
    }
    

    回到上面ServiceLocator的get方法中,我们获取到了$definition,现在是一个数组并非一个对象,所以执行的是:return $this->_components[$id] = Yii::createObject($definition); return回来的就是Request对象: object(yiiwebRequest)

    这个createObject方法,看注释中的描述就是可以理解为一个高级版本的new,因为可以根据字符串(类名),数组(含class信息),匿名函数来创建一个对象,然后返回。

    5、Handle Request

    run中调用的handleRequest()位于yiiwebApplication中,这里主要的操作就是:

    • 从Request中获取用户请求路由
    • 调用这个路由对应的action
    // @param $request yiiwebRequest
    public function handleRequest($request)
    {
        if (empty($this->catchAll)) {
            list ($route, $params) = $request->resolve();
        } else {
            $route = $this->catchAll[0];
            $params = $this->catchAll;
            unset($params[0]);
        }
        //下面暂时省略
    }
    

    catchAll用于系统维护的时候,将所有的请求转发到一处进行处理,默认值为空,所以这里的条件判断进入了list ($route, $params) = $request->resolve(); resolve肯定是在yiiwebRequest中了:

    public function resolve()
    {
        $result = Yii::$app->getUrlManager()->parseRequest($this);
        if ($result !== false) {
            list ($route, $params) = $result;
            if ($this->_queryParams === null) {
                $_GET = $params + $_GET; // preserve numeric keys
            } else {
                $this->_queryParams = $params + $this->_queryParams;
            }
            return [$route, $this->getQueryParams()];
        } else {
            throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
        }
    }
    

    这里通过调用yiiwebUrlManager来解析请求路由,parseRequest方法的参数为Request对象,它的主要任务是:

    • 通过Request对象获取path info
    • 检查是否有跟我们配置的url rule匹配的,有则返回
    • 没有则直接按照默认的方式进行解析

    进入UrlManager:

    public function parseRequest($request)
    {
    	//在enable pretty url的前提下
    	$pathInfo = $request->getPathInfo();
    	//如果在rules中匹配到了 request 直接返回转换后的路由
        foreach ($this->rules as $rule) {
           if (($result = $rule->parseRequest($this, $request)) !== false) {
               return $result;
           }
        }
        //没有则使用默认方式,同时对路由进行检查,判断是否有多与一个的斜线
        //判断是否使用了.html后缀
        
        //代码省略,这里做的就是比较和截取的操作
        
        return [$pathInfo, []];
    }
    

    getPathInfo()先不展开讨论,主要就是从当前请求的http header中获取path info。

    回到yiiwebApplication的handleRequest中:

    public function handleRequest($request)
    {
        //代码简化一下,完整版请查看yiiwebApplication类
        list ($route, $params) = $request->resolve();
        $this->requestedRoute = $route;
        //主要操作
        $result = $this->runAction($route, $params);
        if ($result instanceof Response) {
            return $result;
        } else {
            $response = $this->getResponse();
            if ($result !== null) {
                $response->data = $result;
            }
            return $response;
        }
    }
    

    在获取到属于Yii中的路由(route)信息之后,接下来的主要操作就是执行对应的controller中的action,或者是对应模块(module)中的controller,controller中对应的action。runAction($route,$params)先不展开讨论,主要知道执行完成之后我们获取的是一个yiiwebResponse对象就行了。

    6、回到run方法

    将response信息发送给客户端

    // yiiaseApplication run()
    $response = $this->handleRequest($this->getRequest());
    $response->send();
    return $response->exitStatus;
    

    至此一次完整的请求完成。

  • 相关阅读:
    Knative Serving 进阶: Knative Serving SDK 开发实践
    从求生存到修体系,我在阿里找到了技术人的成长模式
    K8s 学习者绝对不能错过的最全知识图谱(内含 56个知识点链接)
    P1197 [JSOI2008]星球大战
    P1311 选择客栈
    P2822 组合数问题
    贪心 加工生产调度
    P3375 【模板】KMP字符串匹配
    P1025 数的划分
    P1019 单词接龙
  • 原文地址:https://www.cnblogs.com/skyfynn/p/8308997.html
Copyright © 2011-2022 走看看