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
}