没有不值得去解决的问题,也没有不值得去学习的技术!

Investigating Duplicate Requests When the Browser Becomes Unresponsive in Yii 2.0

In Yii 2.0, the browser became unresponsive, which in turn caused the request to be sent twice. The first request had no response, and the second request returned an error (the program prohibits duplicate requests).

1. In Yii 2.0, the browser became unresponsive, which in turn caused the request to be sent twice. The first request had no response, and the second request returned an error (the program prohibits duplicate requests). See Figure 1.

In Yii 2.0, the browser became unresponsive, which in turn caused the request to be sent twice. The first request had no response, and the second request returned an error (the program prohibits duplicate requests).
Figure 1

2. Viewing the details of the first request, under Headers – General, there is no response status code. See Figure 2.

Viewing the details of the first request, under Headers - General, there is no response status code.
Figure 2

3. Viewing the details of the first request, under Response, it fails to load response data. See Figure 3.

Viewing the details of the first request, under Response, it fails to load response data.
Figure 3

4. Checking the Log Messages, execution stopped at E:\wwwroot\channel-pub-api\common\services\WeiboWeiboConnectWebAppAccessTokenService.php:92, when it should have continued. See Figure 4.

Checking the Log Messages, execution stopped at E:\wwwroot\channel-pub-api\common\services\WeiboWeiboConnectWebAppAccessTokenService.php:92, when it should have continued.
Figure 4

5. Looking at the code, line 92: $weiboWeiboConnectWebAppUserAccessToken->save()

PHP

    public function saveModel($data)
    {
        /* 基于微博的微连接的网页应用的用户ID查找状态为启用的单个资源 */
        $weiboWeiboConnectWebAppUserAccessToken = WeiboWeiboConnectWebAppUserAccessToken::find()->where(['weibo_weibo_connect_web_app_user_id' => $data['weiboWeiboConnectWebAppUserId']])->isDeletedNo()->enabled()->one();
        if (!isset($weiboWeiboConnectWebAppUserAccessToken)) {
            $weiboWeiboConnectWebAppUserAccessToken = new WeiboWeiboConnectWebAppUserAccessToken();
        }

        // 设置访问令牌的有效截止时间
        $time = time();
        $accessTokenExpireAt = $time + $data['expires_in'] + Yii::$app->params['weiboAuth']['weiboConnectWebApp']['authPeriod'] - Yii::$app->params['refreshToken']['timeOut'];
        $weiboWeiboConnectWebAppUserAccessToken->attributes = [
            'weibo_weibo_connect_web_app_id' => $data['weiboWeiboConnectWebAppId'],
            'channel_app_source_id' => $data['channelAppSourceId'],
            'channel_app_source_uuid' => $data['channelAppSourceUuid'],
            'weibo_weibo_connect_web_app_user_id' => $data['weiboWeiboConnectWebAppUserId'],
            'weibo_weibo_connect_web_app_user_uuid' => $data['weiboWeiboConnectWebAppUserUuid'],
            'grant_type' => $data['grantType'],
            'access_token' => $data['access_token'],
            'expires_in' => $data['expires_in'],
            'expires_at' => $accessTokenExpireAt,
            'scope' => isset($data['scope']) ? $data['scope'] : '',
            'user_id' => $data['uid'],
            'status' => WeiboWeiboConnectWebAppUserAccessToken::STATUS_ENABLED,
        ];

        if ($weiboWeiboConnectWebAppUserAccessToken->save()) {
            return ['status' => true, 'data' => ['channel_app_source_uuid' => $data['channelAppSourceUuid']]];
        } elseif ($weiboWeiboConnectWebAppUserAccessToken->hasErrors()) {
            $firstError = '';
            foreach ($weiboWeiboConnectWebAppUserAccessToken->getFirstErrors() as $message) {
                $firstError = $message;
                break;
            }
            return ['status' => false, 'code' => 214010, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '214010'), ['first_error' => $firstError]))];
        } elseif (!$weiboWeiboConnectWebAppUserAccessToken->hasErrors()) {
            throw new ServerErrorHttpException('Failed to insert/update the object (microblogging micro-connected web application user\'s access token) for unknown reason.');
        }
    }

6. Printing the object before line 92: $weiboWeiboConnectWebAppUserAccessToken. The printed result is as follows. And the first request returned a 200 response. See Figure 5.

Printing the object before line 92: $weiboWeiboConnectWebAppUserAccessToken. The printed result is as follows. And the first request returned a 200 response.
Figure 5
Plaintext

common\logics\WeiboWeiboConnectWebAppUserAccessToken Object
(
    [_attributes:yii\db\BaseActiveRecord:private] => Array
        (
            [weibo_weibo_connect_web_app_id] => 1
            [channel_app_source_id] => 39
            [channel_app_source_uuid] => c28915a6a8cb11ebba6254ee75d2ebc1
            [weibo_weibo_connect_web_app_user_id] => 6
            [weibo_weibo_connect_web_app_user_uuid] => c28a3efea8cb11ebb69354ee75d2ebc1
            [grant_type] => authorization_code
            [access_token] => 2.00bAKoqCV9i7fEee243e798c0zzui2
            [expires_in] => 2626935
            [expires_at] => 1622394001
            [scope] => 
            [user_id] => 2612590013
            [status] => 1
        )

)


7. Editing the code, line 92: $weiboWeiboConnectWebAppUserAccessToken->save(false), to skip validation before saving the record (the data is trusted), in order to avoid excessive validation time, which leads to a long response time and causes the browser to send duplicate requests. Note: Previously, this was a Redis AR model, which was later changed to a MySQL AR model. Viewing its duration in the browser: 2.57 seconds. See Figure 6 and Figure 7.

Editing the code, line 92: $weiboWeiboConnectWebAppUserAccessToken->save(false), to skip validation before saving the record (the data is trusted), in order to avoid excessive validation time, which leads to a long response time and causes the browser to send duplicate requests.
Figure 6

 

Viewing its duration in the browser: 2.57 seconds.
Figure 7
PHP

    public function saveModel($data)
    {
        /* 基于微博的微连接的网页应用的用户ID查找状态为启用的单个资源 */
        $weiboWeiboConnectWebAppUserAccessToken = WeiboWeiboConnectWebAppUserAccessToken::find()->where(['weibo_weibo_connect_web_app_user_id' => $data['weiboWeiboConnectWebAppUserId']])->isDeletedNo()->enabled()->one();
        if (!isset($weiboWeiboConnectWebAppUserAccessToken)) {
            $weiboWeiboConnectWebAppUserAccessToken = new WeiboWeiboConnectWebAppUserAccessToken();
        }

        // 设置访问令牌的有效截止时间
        $time = time();
        $accessTokenExpireAt = $time + $data['expires_in'] + Yii::$app->params['weiboAuth']['weiboConnectWebApp']['authPeriod'] - Yii::$app->params['refreshToken']['timeOut'];
        $weiboWeiboConnectWebAppUserAccessToken->attributes = [
            'weibo_weibo_connect_web_app_id' => $data['weiboWeiboConnectWebAppId'],
            'channel_app_source_id' => $data['channelAppSourceId'],
            'channel_app_source_uuid' => $data['channelAppSourceUuid'],
            'weibo_weibo_connect_web_app_user_id' => $data['weiboWeiboConnectWebAppUserId'],
            'weibo_weibo_connect_web_app_user_uuid' => $data['weiboWeiboConnectWebAppUserUuid'],
            'grant_type' => $data['grantType'],
            'access_token' => $data['access_token'],
            'expires_in' => $data['expires_in'],
            'expires_at' => $accessTokenExpireAt,
            'scope' => isset($data['scope']) ? $data['scope'] : '',
            'user_id' => $data['uid'],
            'status' => WeiboWeiboConnectWebAppUserAccessToken::STATUS_ENABLED,
        ];

        if ($weiboWeiboConnectWebAppUserAccessToken->save(false)) {
            return ['status' => true, 'data' => ['channel_app_source_uuid' => $data['channelAppSourceUuid']]];
        } elseif ($weiboWeiboConnectWebAppUserAccessToken->hasErrors()) {
            $firstError = '';
            foreach ($weiboWeiboConnectWebAppUserAccessToken->getFirstErrors() as $message) {
                $firstError = $message;
                break;
            }
            return ['status' => false, 'code' => 214010, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '214010'), ['first_error' => $firstError]))];
        } elseif (!$weiboWeiboConnectWebAppUserAccessToken->hasErrors()) {
            throw new ServerErrorHttpException('Failed to insert/update the object (microblogging micro-connected web application user\'s access token) for unknown reason.');
        }
    }

8. Checking the response time of the first request when the duplicate requests occurred, both are over 2500 ms. See Figure 8.

Checking the response time of the first request when the duplicate requests occurred, both are over 2500 ms.
Figure 8

9. Reverting the code, line 92: $weiboWeiboConnectWebAppUserAccessToken->save(). When the environment is set to Production, the response time is not excessively long either. Viewing its duration in the browser: 1.56 seconds. This leads to the conclusion: whether validation is enabled certainly affects the response time, but the root cause still lies in the environment setting (Development). See Figure 9.

Reverting the code, line 92: $weiboWeiboConnectWebAppUserAccessToken->save(). When the environment is set to Production, the response time is not excessively long either. Viewing its duration in the browser: 1.56 seconds. This leads to the conclusion: whether validation is enabled certainly affects the response time, but the root cause still lies in the environment setting (Development).
Figure 9

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系