❥(^_-) Yii2框架源码解析之请求和响应

应用入口

首先,yii框架的应用的整个运行过程有一个生命周期,生命周期的状态是从0到6,代表从应用的开始到结束。中间不同的生命周期,会使用框架中的事件机制触发不同的生命周期方法(这里只讲整个请求和响应的流程,具体涉及到的事件后面的博文再讲)。这里把生命周期列出来,大家有个印象。

const STATE_BEGIN = 0;

const STATE_INIT = 1;

const STATE_BEFORE_REQUEST = 2;

const STATE_HANDLING_REQUEST = 3;

const STATE_AFTER_REQUEST = 4;

const STATE_SENDING_RESPONSE = 5;

const STATE_END = 6;

从英文的字面意思就能看出,不同的常量表示哪一个生命周期,这里不做过多赘述。整个应用的入口是run()方法:

    public function run()
    {
        // 这里是运行整个应用的入口

        // 按照整个应用的生命周期去进行相应的操作。
        try {
            // 请求之前(即将发送请求)
            $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;
        } catch (ExitException $e) {
            // 整个生命周期过程中,如果出现错误,调用应用结束的方法(区别直接exit,这样会完善一个应用的生命周期)。
            $this->end($e->statusCode, isset($response) ? $response : null);
            return $e->statusCode;
        }
    }

请求

我们重点看"请求之中",框架的处理:

public function handleRequest($request)
    {
        // 处理请求,并返回响应实例。

        // 如果$this->catchAll不为空,表示单个路由操作处理所有用户的所有请求。一般用在维护模式。
        // 如果$this->catchAll为空,表示正常路由解析流程。
        if (empty($this->catchAll)) {
            try {
                list($route, $params) = $request->resolve();
            } catch (UrlNormalizerRedirectException $e) {
                // 发生错误,url重定向。
                $url = $e->url;
                if (is_array($url)) {
                    if (isset($url[0])) {
                        // ensure the route is absolute
                        $url[0] = '/' . ltrim($url[0], '/');
                    }
                    $url += $request->getQueryParams();
                }

                return $this->getResponse()->redirect(Url::to($url, $e->scheme), $e->statusCode);
            }
        } else {
            $route = $this->catchAll[0];
            $params = $this->catchAll;
            unset($params[0]);
        }

        // 现在已经成功解析了路由和参数
        try {
            Yii::debug("Route requested: '$route'", __METHOD__);
            $this->requestedRoute = $route;

            // 构造请求控制器实例,将执行控制器方法。$result即控制器方法运行结果。
            // $result如果是response的实例,就直接返回。否则将其赋值到data,再返回。
            $result = $this->runAction($route, $params);
            if ($result instanceof Response) {
                return $result;
            }

            $response = $this->getResponse();
            if ($result !== null) {
                $response->data = $result;
            }

            return $response;
        } catch (InvalidRouteException $e) {
            throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'), $e->getCode(), $e);
        }
    }

应用"请求之中",调用了handleRequest()方法,入参是当前请求实例。通过解析请求实例$request中的请求参数(当前请求url),获取请求路由和参数,并根据路由构造控制器实例,执行路由中对应的控制器方法。最后,返回应用响应实例。

然后我们看到,在应用结束之前,执行了响应类的send方法:

$response->send();

响应

我们根据send()方法,看到响应做了哪些事情:

    public function send()
    {
        // 向客户端响应

        // 已经发送就直接返回
        if ($this->isSent) {
            return;
        }

        // 响应前的事件
        $this->trigger(self::EVENT_BEFORE_SEND);

        // 准备工作
        $this->prepare();

        // 准备工作之后的事件
        $this->trigger(self::EVENT_AFTER_PREPARE);

        // 发送响应头(通过header函数)
        $this->sendHeaders();

        // 发送响应内容(通过直接echo)
        $this->sendContent();

        // 发送之后的事件
        $this->trigger(self::EVENT_AFTER_SEND);
        $this->isSent = true;
    }

重点分析一下响应内容的发送:

    protected function sendContent()
    {
        // 如果不是文件流,则直接 echo 内容。
        if ($this->stream === null) {
            echo $this->content;
            return;
        }

        // 设置不超时
        if (function_exists('set_time_limit')) {
            set_time_limit(0); // Reset time limit for big files
        } else {
            Yii::warning('set_time_limit() is not available', __METHOD__);
        }

        $chunkSize = 8 * 1024 * 1024; // 8MB per chunk

        // 如果是数组,表示固定文件读取内容的开始和结尾。
        if (is_array($this->stream)) {
            list($handle, $begin, $end) = $this->stream;
            // fseek() 函数在打开的文件中定位。
            fseek($handle, $begin); // 将文件$handle指针移动到$begin位置。

            // 文件没有到达末尾,并且当前位置小于设置的文件结束位置。则循环
            while (!feof($handle) && ($pos = ftell($handle)) <= $end) {
                // 如果当前位置+8M大于结束位置。表示就将输出单位变为$end - $pos + 1。这个正好表示文件需要结束总大小+1。表示不需要下次循环了,并且减少了文件的扫描范围。
                if ($pos + $chunkSize > $end) {
                    $chunkSize = $end - $pos + 1;
                }
                echo fread($handle, $chunkSize);
                flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
            }
            fclose($handle);
        } else {
            // eof() 函数检查是否已到达文件末尾(EOF)。
            // 如果没有到达文件末尾,则以8M为一个单位,输出文件内容。
            while (!feof($this->stream)) {
                echo fread($this->stream, $chunkSize);
                flush();
            }
            fclose($this->stream);
        }
    }

可以看到,响应的内容支持文件流和字符串两种方式。那么通过阅读源码,我们知道,在控制器的方法中可以这样写:

    public function actionIndex()
    {
        $responseContent = [
            "name" => "Jone",
            "pass" => "123456",
        ];
        \Yii::$app->response->content = json_encode($responseContent);
        return;
    }

当然,如果你return了一个字符串,则会以你return的这个字符串为主。它会作为response实例的data属性,会在返回前初始化,覆盖掉原来的content属性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值