thinkphp5.1 workerman核心修改攻略

1、thinkphp原来的Response设置header方式不管用了,为什么正常开发的时候却又好用?

\vendor\topthink\think-worker\src\Application.php的worker接管了Response getContent解析,无论\application下如何业务逻辑处理,只要Controller return返回的Response交给worker处理即可,我们打开\vendor\topthink\think-worker\src\Application.php,看到worker方法中通过WorkerHttp对header重新处理,workerman对header只能通过WorkerHttp。

public function worker($connection)
    {
        try {
            ob_start();
            // 重置应用的开始时间和内存占用
            $this->beginTime = microtime(true);
            $this->beginMem  = memory_get_usage();

            // 销毁当前请求对象实例
            $this->delete('think\Request');

            $pathinfo = ltrim(strpos($_SERVER['REQUEST_URI'], '?') ? strstr($_SERVER['REQUEST_URI'], '?', true) : $_SERVER['REQUEST_URI'], '/');

            $this->request->setPathinfo($pathinfo);

            if ($this->config->get('session.auto_start')) {
                WorkerHttp::sessionStart();
            }

            // 更新请求对象实例
            $this->route->setRequest($this->request);

            $response = $this->run();
            $response->send();
            $content = ob_get_clean();

            // Trace调试注入
            if ($this->env->get('app_trace', $this->config->get('app_trace'))) {
                $this->debug->inject($response, $content);
            }

            $this->httpResponseCode($response->getCode());

            foreach ($response->getHeader() as $name => $val) {
                // 发送头部信息
                WorkerHttp::header($name . (!is_null($val) ? ':' . $val : ''));
            }

            $connection->send($content);
        } catch (HttpException $e) {
            $this->exception($connection, $e);
        } catch (\Exception $e) {
            $this->exception($connection, $e);
        } catch (\Throwable $e) {
            $this->exception($connection, $e);
        }
    }

如果run()出现异常,会终止一下执行,跳到catch,最终执行$this->exception,并未执行WorkerHttp::header,此时如果配置'exception_handle' => \app\core\exception\Http::class,在\app\core\exception\Http::class中调用Response 设置header是无效的(除非引用,WorkerHttp::header,但为了减少依赖,我能不在exception_handle配置),所以在\vendor\topthink\think-worker\src\Application.php中改了exception方法

protected function exception($connection, $e)
    {
        if ($e instanceof \Exception) {
            $handler = Error::getExceptionHandler();
            $handler->report($e);

            $resp    = $handler->render($e);
            $content = $resp->getContent();
            $code    = $resp->getCode();

            //begin by qissen
            //性质:新增
            //作用:必须通过设置WorkerHttp才可以设置头 2019年3月25日
            foreach ($resp->getHeader() as $name => $val) {
                // 发送头部信息
                WorkerHttp::header($name . (!is_null($val) ? ':' . $val : ''));
            }
            //end

            $this->httpResponseCode($code);
            $connection->send($content);
        } else {
            $this->httpResponseCode(500);
            $connection->send($e->getMessage());
        }
    }

这样就可以正常接管异常处理了

class Http extends Handle
{
    public function render(Exception $e)
    {
        if ($e instanceof HttpException) {
            if(config('app_debug')) {
                //交由系统处理
                return parent::render($e);
            }
            $statusCode = $e->getStatusCode();
            $response = Response::create((new Result())->code(CODE::ERROR_SYSTEM)->msg($e->getMessage())->data(), 'json')->code($statusCode);
            return $response;
        }
        else if ($e instanceof AppException) {
            $statusCode = $e->getStatusCode();
            $response = Response::create((new Result())->code($statusCode)->msg($e->getMessage())->data(), 'json')->code(200);
            return $response;
        }
    }
}

Exception在找不到异常类的情况下 可以接受自定义异常类。

2、middleware有bug?

\thinkphp\library\think\App.php下的init是初始化模块包括模块配置的方法,thinkphp机制是先加载公共config包括middleware,provider,但是模块是每次访问某模块根据模块再去加载的,举个例子,一个/user/index/index,开始init(),之后init('user')。
好,接下来我们看下\thinkphp\library\think\App.php init方法

if (is_file($path . 'middleware.php')) {
                $middleware = include $path . 'middleware.php';
                if (is_array($middleware)) {
                    $this->middleware->import($middleware);
                }
            }

这里,我们发现了include,也就是说,每次客户访问的时候,都会执行include,这样就会导致重复import middleware,最后出现一系列问题;我们可以采取两种方案
a)将include 修改为include_once
b)init只执行一次
为了避免,多次执行init带来的其他问题,比如provider,我们最终选择了方案b,php cli情况下,将各个模块加载完成,修改了代码如下
a)\thinkphp\library\think\App.php init方法 修改两处

public function init($module = '')
    {
        //begin by qissen
        //修改:新增
        //作用检查模块初始化
        $_module = $module;
        if(!empty($module) && in_array($module, $this->moduleInitializeds)) {
            return;
        }
        //end


        // 定位模块目录
        $module = $module ? $module . DIRECTORY_SEPARATOR : '';
        $path   = $this->appPath . $module;

        // 加载初始化文件
        if (is_file($path . 'init.php')) {
            include $path . 'init.php';
        } elseif (is_file($this->runtimePath . $module . 'init.php')) {
            include $this->runtimePath . $module . 'init.php';
        } else {
            // 加载行为扩展文件
            if (is_file($path . 'tags.php')) {
                $tags = include $path . 'tags.php';
                if (is_array($tags)) {
                    $this->hook->import($tags);
                }
            }

            // 加载公共文件
            if (is_file($path . 'common.php')) {
                include_once $path . 'common.php';
            }

            if ('' == $module) {
                // 加载系统助手函数
                include $this->thinkPath . 'helper.php';
            }

            // 加载中间件
            if (is_file($path . 'middleware.php')) {
                $middleware = include $path . 'middleware.php';
                if (is_array($middleware)) {
                    //begin by qissen
                    //修改:修改了代码
                    //作用:根据模块导入对应的middleware,并根据模块分类
                    if(!$_module) {
                        $this->middleware->import($middleware);
                    }
                    else {
                        $this->middleware->import($middleware, $_module);
                    }
                    //
                }
            }

            // 注册服务的容器对象实例
            if (is_file($path . 'provider.php')) {
                $provider = include $path . 'provider.php';
                if (is_array($provider)) {
                    $this->bindTo($provider);
                }
            }

            // 自动读取配置文件
            if (is_dir($path . 'config')) {
                $dir = $path . 'config' . DIRECTORY_SEPARATOR;
            } elseif (is_dir($this->configPath . $module)) {
                $dir = $this->configPath . $module;
            }

            $files = isset($dir) ? scandir($dir) : [];

            foreach ($files as $file) {
                if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $this->configExt) {
                    $this->config->load($dir . $file, pathinfo($file, PATHINFO_FILENAME));
                }
            }
        }

        $this->setModulePath($path);

        if ($module) {
            // 对容器中的对象实例进行配置更新
            $this->containerConfigUpdate($module);
        }
    }

b)\thinkphp\library\think\App.php initialize方法 修改一处

public function initialize()
    {
        if ($this->initialized) {
            return;
        }

        $this->initialized = true;
        $this->beginTime   = microtime(true);
        $this->beginMem    = memory_get_usage();

        $this->rootPath    = dirname($this->appPath) . DIRECTORY_SEPARATOR;
        $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
        $this->routePath   = $this->rootPath . 'route' . DIRECTORY_SEPARATOR;
        $this->configPath  = $this->rootPath . 'config' . DIRECTORY_SEPARATOR;

        static::setInstance($this);

        $this->instance('app', $this);

        $this->configExt = $this->env->get('config_ext', '.php');

        // 加载惯例配置文件
        $this->config->set(include $this->thinkPath . 'convention.php');

        // 设置路径环境变量
        $this->env->set([
            'think_path'   => $this->thinkPath,
            'root_path'    => $this->rootPath,
            'app_path'     => $this->appPath,
            'config_path'  => $this->configPath,
            'route_path'   => $this->routePath,
            'runtime_path' => $this->runtimePath,
            'extend_path'  => $this->rootPath . 'extend' . DIRECTORY_SEPARATOR,
            'vendor_path'  => $this->rootPath . 'vendor' . DIRECTORY_SEPARATOR,
        ]);

        // 加载环境变量配置文件
        if (is_file($this->rootPath . '.env')) {
            $this->env->load($this->rootPath . '.env');
        }

        $this->namespace = $this->env->get('app_namespace', $this->namespace);
        $this->env->set('app_namespace', $this->namespace);

        // 注册应用命名空间
        Loader::addNamespace($this->namespace, $this->appPath);

        // 初始化应用
        $this->init();

        // begin by qissen
        // 修改:新增
        // 作用:初始化模块
        $moduleDirs = scandir($this->appPath);
        foreach ($moduleDirs as $module) {
            if(is_dir($this->appPath.$module) && !in_array($module, $this->config('app.deny_module_list')) && !in_array($module, ['.', '..'])) {
                $this->init($module);
                $this->moduleInitializeds[] = $module;
            }
        }
        //end

        // 开启类名后缀
        $this->suffix = $this->config('app.class_suffix');

        // 应用调试模式
        $this->appDebug = $this->env->get('app_debug', $this->config('app.app_debug'));
        $this->env->set('app_debug', $this->appDebug);

        if (!$this->appDebug) {
            ini_set('display_errors', 'Off');
        } elseif (PHP_SAPI != 'cli') {
            //重新申请一块比较大的buffer
            if (ob_get_level() > 0) {
                $output = ob_get_clean();
            }
            ob_start();
            if (!empty($output)) {
                echo $output;
            }
        }

        // 注册异常处理类
        if ($this->config('app.exception_handle')) {
            Error::setExceptionHandler($this->config('app.exception_handle'));
        }

        // 注册根命名空间
        if (!empty($this->config('app.root_namespace'))) {
            Loader::addNamespace($this->config('app.root_namespace'));
        }

        // 加载composer autofile文件
        Loader::loadComposerAutoloadFiles();

        // 注册类库别名
        Loader::addClassAlias($this->config->pull('alias'));

        // 数据库配置初始化
        Db::init($this->config->pull('database'));

        // 设置系统时区
        date_default_timezone_set($this->config('app.default_timezone'));

        // 读取语言包
        $this->loadLangPack();

        // 路由初始化
        $this->routeInit();
    }

c)\thinkphp\library\think\App.php 添加了属性

//begin by qissen
    //修改:新增
    //作用:如果当前队列存在某个模块,则为已经初始化
    protected $moduleInitializeds = [];
    //end

d)\thinkphp\library\think\App.php run方法 修改一处

public function run()
    {
        try {
            // 初始化应用
            $this->initialize();

            // 监听app_init
            $this->hook->listen('app_init');

            if ($this->bindModule) {
                // 模块/控制器绑定
                $this->route->bind($this->bindModule);
            } elseif ($this->config('app.auto_bind_module')) {
                // 入口自动绑定
                $name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME);
                if ($name && 'index' != $name && is_dir($this->appPath . $name)) {
                    $this->route->bind($name);
                }
            }

            // 监听app_dispatch
            $this->hook->listen('app_dispatch');

            $dispatch = $this->dispatch;

            if (empty($dispatch)) {
                // 路由检测
                $dispatch = $this->routeCheck()->init();
            }

            // 记录当前调度信息
            $this->request->dispatch($dispatch);

            // 记录路由和请求信息
            if ($this->appDebug) {
                $this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true));
                $this->log('[ HEADER ] ' . var_export($this->request->header(), true));
                $this->log('[ PARAM ] ' . var_export($this->request->param(), true));
            }

            // 监听app_begin
            $this->hook->listen('app_begin');

            // 请求缓存检查
            $this->checkRequestCache(
                $this->config('request_cache'),
                $this->config('request_cache_expire'),
                $this->config('request_cache_except')
            );

            $data = null;
        } catch (HttpResponseException $exception) {
            $dispatch = null;
            $data     = $exception->getResponse();
        }

        //begin by qissen
        //修改:修改
        //作用:根据模块调度middleware
        $this->middleware->add(function (Request $request, $next) use ($dispatch, $data) {
            return is_null($data) ? $dispatch->run() : $data;
        }, $dispatch->getDispatch()[0]);

        $response = $this->middleware->dispatch($this->request, $dispatch->getDispatch()[0]);
        //end

        // 监听app_end
        $this->hook->listen('app_end', $response);

        return $response;
    }

e)\thinkphp\library\think\Middleware.php resolve方法修改

//begin by qissen
    //修改:
    //1、添加$index引用,用来记录次数
    //2、不能用array_shift,因为$this->middleware是静态实例$this->queue也是静态实例属性,两个用户同时访问会导致冲突,比如一个用户array_shift一个middleware,会导致另外一个用户丢失middleware
    //作用:用来记录次数
    protected function resolve($type = 'route', &$index)
    {
        return function (Request $request) use ($type, &$index) {
            $middleware = $this->queue[$type][$index];

            if (null === $middleware) {
                throw new InvalidArgumentException('The queue was exhausted, with no response returned');
            }
            list($call, $param) = $middleware;
            try {
                $index++;
                $response = call_user_func_array($call, [$request, $this->resolve($type, $index), $param]);
            } catch (HttpResponseException $exception) {
                $response = $exception->getResponse();
            }

            if (!$response instanceof Response) {
                throw new LogicException('The middleware must return Response instance');
            }
            return $response;
        };
    }
    //end

f)\thinkphp\library\think\Middleware.php dispatch方法修改

public function dispatch(Request $request, $type = 'route')
    {
        //begin by qissen
        //修改:
        //作用:传一个引用变量,保证递归访问全局有效
        $index = 0;
        return call_user_func($this->resolve($type, $index), $request);
        //end
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351