基于swoole打造laravel的rpc框架
简介
为了深入了解swoole是如何对框架进行加速与提供php服务的功能,以及了解rpc微服务的原理,在基于composer的 psr-4 规则下,搭建一套简单的类似 laravel 的框架,起名为:swostar,swostar框架中以 IOC 容器与Provider为核心,对所有的服务进行单利绑定(bind)与服务解析(make),Provider加载要提供的服务。
其中提供的主要核心功能有:路由解析,控制器的加载,event事件注册,rpc服务提供,consul服务提供
目录结构如下:
├─app
├─├─Provider ----- 服务提供者
├─├─Listener ----- event事件监听
├─├─Http --------- 框架控制器层
├─├─RPC ---------- rpc客户端与服务端
├─bin ------------ 框架启动入口
├─config --------- 框架配置
├─route ---------- 路由定义
├─vendor
├─├─core
├─├─├─swostar-frame --------- 框架核心代码
├─├─├─├─src
├─├─├─├─├─Config--------- 用于加载app/config的配置文件
├─├─├─├─├─Consul--------- consul应用服务
├─├─├─├─├─Container------ IOC容器的绑定与解析
├─├─├─├─├─Event---------- event事件加载
├─├─├─├─├─Foundation----- 框架应用的核心入口,加载框架与驱动
├─├─├─├─├─Routes--------- 路由模块
├─├─├─├─├─RPC------------ rpc客户端与服务端
├─├─├─├─├─Server--------- 提供swoole的http与tcp服务
获取源码:
git clone git@gitee.com:dear-q/swostar.git
框架结构图:
项目搭建:
1 . 使用composer生成vender
1 . 1创建项目 ,当前的项目为goods, 新建两个文件夹
#当前的项目
mkdir goods
#用于composer 生成 vendor
mkdir swostar
1 . 2 分别在两个目录中进行composer初始化
composer init
执行后输入:core/swostar
1 . 3 .运行创建后,分别更改内容更改如下
vim goods/composer.json
{
"name": "core/swostar",
"authors": [
{
"name": "Dear",
"email": "15626109675@163.com"
}
],
"require": {
"core/swostar-frame": "master-dev"
},
"repositories": {
"Dear": {
"type": "path",
"url": "../swostar"
}
},
"autoload": {
"psr-4": {
"App\\":"app/"
}
}
}
vim swostar/composer.json
{
"name": "core/swostar-frame",
"authors": [
{
"name": "Dear",
"email": "15626109675@163.com"
}
],
"require": {},
"autoload": {
"psr-4": {
"SwoStar\\":"./src/"
},
"files" : [
"src/Supper/Helper.php"
]
}
}
1 . 4 . 回到goods中加载composer生成vendor,composer会根据 composer.json中的repositories定义的 url 把swostar的文件加载到vendor中,同时会生成composer的配置信息
composer require core/swostar-frame:master-dev
composer加载好后,就可以再vendor中开始编写加载框架的代码了
2 . 创建框架入口,加载app/config与驱动框架
2 . 1 启动框架 /bin/swostar
<?php
//加载vendor
require dirname(__DIR__).'/vendor/autoload.php';
//运行框架
(new \SwoStar\Foundation\Application(dirname(__DIR__)))->run($argv);
2 . 2 IOC容器注册与解析
IOC的原理其实就是把创建好的对象,存储进数组当中,当解析的时候,通过标识从数组中获取到对象返回去而已,从而减少对象的创建而带来的消耗
SwoStar/Container/Container.php
<?php
namespace SwoStar\Container;
use Closure;
use Exception;
/**
* 容器
*/
class Container
{
// 0. 单例
protected static $instance;
// 1. 容器,用于记录创建好的对象信息或者数据
protected $bindings = [];
// 1. 容器,用于记录创建好的对象信息或者数据
protected $instances = [];
/**
* 容器绑定的方法 : 就是把一个对象存储进数组中
* @param string $abstract 标识
* @param object $object 实例对象或者闭包
*/
public function bind($abstract, $object)
{
// 标识要绑定
// 1. 就是一个对象
// 2. 闭包的方式
// 3. 类对象的字符串 (类的地址)
$this->bindings[$abstract] = $object;
}
/**
* 从容器中解析实例对象或者闭包
* @param string $abstract 标识
* @param array $parameters 传递的参数
* @return object 是一个闭包或者对象
*/
public function make($abstract, $parameters = [])
{
//如果已经记录了对象,则返回对象
//参数如:config
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
//传递的是命名空间,则创建对象
//参数如:SwoStar\Consul\Response
if (!$this->has($abstract)) {
// 如果不存在自行
// 选择返回, 可以抛出一个异常
// throw new Exception('没有找到这个容器对象'.$abstract, 500);
$object = $abstract;
} else {
//获取bind()中绑定好的对象或者属性信息
$object = $this->bindings[$abstract];
}
// 在这个容器中是否存在
// 1. 判断是否一个为对象
// 2. 闭包的方式
if ($object instanceof Closure) {
return $object();
}
// 3. 类对象的字符串 (类的地址)
//创建对象并且记录数组中
return $this->instances[$abstract] = (is_object($object)) ? $object : new $object(...$parameters) ;
}
/**
* 判断是否在容器中
* 容器很多便于扩展
* 可能在其他场景中会用到
* @param $abstract
* @return bool
*/
public function has($abstract)
{
return isset($this->bindings[$abstract]);
}
/**
* 单例创建
* @return mixed
*/
public static function getInstance()
{
if (is_null(static::$instance)) {
static::$instance = new static;
}
return static::$instance;
}
/**
* @param null $container
* @return null
*/
public static function setInstance($container = null)
{
return static::$instance = $container;
}
}
2 . 3 框架应用的核心入口,加载框架与驱动
2 . 3 . 1
SwoStar/Foundation/Application.php : 加载应用框架,加载服务提供者,容器绑定
- 主要是加载 app/config的配置文件,通过app.php提供的服务提供者进行服务应用加载
- 启动http服务
- 提供tcp服务
<?php
namespace SwoStar\Foundation;
use SwoStar\Console\Input;
use SwoStar\Container\Container;
use SwoStar\Rpc\RpcServer;
use SwoStar\Server\Http\Server as HttpServer;
class Application extends Container
{
protected const SWOSTAR_WELCOME = "
_____ _____ ___
/ __/ ____ / __/ __/ /__ ___ __ __ __
\__ \ | | /| / / / __ \ \__ \ /_ ___/ / _` | / /_/ /
__/ / | |/ |/ / / /_/ / __/ / / /_ | (_| | / ___/
/___/ |__/\__/ \____/ /___/ \___/ \___/\_| /__/
";
protected $basePath = "";
/**
* 定义要加载的驱动
* @var array
*/
protected $bootstraps = [
//加载config目录下的所有配置信息
Bootstrap\LoadConfiguration::class,
//加载驱动,加载服务提供者
Bootstrap\ServerPrivoder::class
];
/**
* 在启动http服务之前,先加载框架,加载驱动
* 注意:这里并没有加载到onworkstart函数中,因为在启动http服务之前加载框架
* 也相当于加载到了常驻内存中,不过还是有些区别,也可以自行加载到workstart中
* Application constructor.
* @param $path .值为:/docker/www/work/18/activities
*/
function __construct($path)
{
if (!empty($path)) {
$this->setBasePath($path);
}
//设置单利
self::setInstance($this);
// 加载框架驱动
$this->bootstrap();
Input::info(self::SWOSTAR_WELCOME, "启动项目");
}
/**
* 加载框架驱动
*/
public function bootstrap()
{
foreach ($this->bootstraps as $key => $bootstrap) {
(new $bootstrap())->bootstrap($this);
}
}
/**
* 运行框架,启动http服务
* @param $argv
* @throws \Exception
*/
public function run($argv)
{
$server = null;
switch ($argv[1]) {
case 'http:start':
$server = new HttpServer($this);
break;
default:
break;
}
// 判断是否开启rpc
if ($this->make("config")->get("server.rpc.flag")) {
new RpcServer($this,$server);
}
// 就是启动swostar
$server->start();
}
/**
* 获取当前服务器的config文件夹所在的完整配置路径
* 返回数据:/docker/www/work/18/activities/config
* @return string
*/
public function getConfigPath()
{
return $this->getBasePath()."/config";
}
/**
* 获取当前服务器中项目文件夹所在的完整配置路径
* 返回数据:/docker/www/work/18/activities
* @return string
*/
public function getBasePath()
{
return $this->basePath;
}
/**
* 设置当前服务器中项目文件夹所在的完整配置路径
* 存储的数据: /docker/www/work/18/activities
* @param $path
*/
public function setBasePath($path)
{
$this->basePath = \rtrim($path, '\/');
}
}
2 . 3 . 2 (核)
SwoStar/Foundation/Bootstrap/LoadConfiguration.php : 加载app/config目录下的所有配置,并转换成数组绑定到容器中
<?php
namespace SwoStar\Foundation\Bootstrap;
use SwoStar\Config\Config;
use SwoStar\Foundation\Application;
class LoadConfiguration
{
/**
* 绑定容器为config,用于加载config配置文件中定义的配置信息
* @param Application $app
*/
public function bootstrap(Application $app)
{
$app->bind('config', new Config($app->getConfigPath()));
}
}
2 . 3 . 3
SwoStar/Foundation/config/config.php : 具体的操作类,进行绑定与获取config的配置信息
<?php
namespace SwoStar\Config;
class Config
{
protected $itmes = [];
function __construct($path)
{
// 读取配置
$this->itmes = $this->phpParser($path);
}
/**
* 读取PHP文件类型的配置文件
* $path路径为当前服务器的config文件位置: /docker/www/work/18/activities/config
* @param $path
* @return null
*/
protected function phpParser($path)
{
// 1 . 找到当前config目录中下的所有配置文件
// 此处跳过多级的情况
$files = scandir($path);
$data = null;
// 2. 读取文件信息
foreach ($files as $key => $file) {
// 2 . 1 过滤操作
if ($file === '.' || $file === '..') {
continue;
}
// 2.2 获取文件名
$filename = \stristr($file, ".php", true);
// 2.3 拼接路径,加载文件信息到框架中
//路径 : /docker/www/work/18/activities/config/service.php
$data[$filename] = include $path."/".$file;
}
// 3. 返回
return $data;
}
/**
* 解析配置文件的配置信息
* key.key2.key3
* @param $keys
* @return array|mixed|null
*/
public function get($keys)
{
$data = $this->itmes;
foreach (\explode('.', $keys) as $key => $value) {
$data = $data[$value];
}
return $data;
}
}
2 . 3 . 4 (核)
SwoStar/Foundation/Bootstrap/ServerPrivoder.php : 对app/config/app.php中定义的服务提供者(priovders)进行注册与驱动
<?php
namespace SwoStar\Foundation\Bootstrap;
use SwoStar\Foundation\Application;
class ServerPrivoder
{
/**
* 加载驱动,加载服务提供者
* @param Application $app
*/
public function bootstrap(Application $app)
{
//获取服务提供者的配置信息
$priovders = $app->make('config')->get('app.priovders');
// 先是 register,后boot
foreach ($priovders as $key => $priovder) {
$prio = new $priovder($app);
//注册
$prio->register();
//驱动
$prio->boot();
}
}
}
2 . 4 加载路由,解析控制器
对config/app.php中的RouteServerProvider进行路由解析,并把route文件加载进来
2 . 4 . 1
App/Providers/RouteServerProvider.php : 定义路由文件,控制器Controller的工作命名空间
<?php
namespace App\Providers;
use SwoStar\Routes\RouteServerProvider as ServerProvider;
class RouteServerProvider extends ServerProvider
{
public function boot()
{
$this->mapRouteHttp();
//路由服务提供者,绑定路由的加载与解析
parent::boot();
}
/**
* http服务的工作路径配置
*/
public function mapRouteHttp()
{
$this->map['http'] = [
//工作命名空间
'namespace' => 'App\Http\Controller',
//路由文件
'path' => $this->app->getBasePath()."/route/http.php",
//标签
'flag' => 'http'
];
}
/**
* websocket服务的工作路径配置
*/
public function mapRouteWS()
{
$this->map['websocket'] = [
'namespace' => 'App\WebSocket\Controller',
'path' => $this->app->getBasePath()."/route/websocket.php",
'flag' => 'websocket'
];
}
}
2 . 4 . 2
SwoStar/Route/ServerPriovder.php : 路由服务提供者,绑定路由的加载与解析 , 绑定在项目启动的时候绑定,解析则有swoole中请求http的 onRequest 里解析
<?php
namespace SwoStar\Routes;
use SwoStar\Supper\ServerPriovder;
/**
* 路由服务提供者,绑定路由的加载与解析
*/
class RouteServerProvider extends ServerPriovder
{
// 记录路由文件,以及路由信息
protected $map;
public function boot()
{
//注册路由,并且绑定路由对象到数组中
$this->app->bind('route', Route::getInstance()->registerRoute($this->map));
}
}
2 . 4 . 3 (核)
SwoStar/Route/Route.php : 这里是具体的进行route操作的类,注册与解析路由的具体方法
registerRoute方法:先加载路由文件,才行执行后面两个方法
addRoute方法:根据在路由文件中添加的路由,加载到控制器中的具体方法,并且绑定到内存中
match方法:路由请求解析与校验的方法 , 在swoole中的 onRequest 中进行解析调用
<?php
namespace SwoStar\Routes;
class Route
{
protected static $instance = null;
// 路由本质实现是会有一个容器在存储解析之后的路由
protected $routes = [];
// 定义了访问的类型
protected $verbs = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
// 区分服务类型
protected $flag;
protected $namespace;
protected function __construct( )
{
}
public static function getInstance()
{
if (\is_null(self::$instance)) {
self::$instance = new static();
}
return self::$instance ;
}
public function get($uri, $action)
{
return $this->addRoute(['GET'], $uri, $action);
}
public function post($uri, $action)
{
return $this->addRoute(['POST'], $uri, $action);
}
public function any($uri, $action)
{
return $this->addRoute(self::$verbs, $uri, $action);
}
/**
* 注册路由,根据在路由文件中添加的路由,加载到控制器中的具体方法,并且绑定到内存中
* @param $methods
* @param $uri
* @param $action
* @return $this
*/
protected function addRoute($methods, $uri, $action)
{
foreach ($methods as $method ) {
if ($action instanceof \Closure) {
$this->routes[$this->flag][$method][$uri] = $action;
} else {
$this->routes[$this->flag][$method][$uri] = $this->namespace."\\".$action;
}
}
return $this;
}
/**
* 路由请求解析与校验的方法 , 在swoole中的 onRequest 中进行解析调用
* @param $pathinfo
* @param $flag
* @param $method
* @return mixed|string
*/
public function match($pathinfo, $flag, $method)
{
$action = null;
// 根据传递服务标识,请求类型查找route
foreach ($this->routes[$flag][$method] as $uri => $value) {
// 保持route标识与pathinfo一致性
$uri = ($uri && \substr($uri, 0, 1) != '/') ? "/".$uri : $uri;
if ($pathinfo === $uri) {
$action = $value;
break;
}
}
// 判断是否查找到route
if (!empty($action)) {
// 执行方法操作
return $this->runAction($action);
}
dd($action, "没有查找到方法");
return "404";
}
/**
* 这是运行实际方法
* @param $action
* @return mixed
*/
public function runAction($action)
{
if ($action instanceof \Closure) {
return $action();
} else {
$arr = \explode("@", $action);
$class = new $arr[0]();
return $class->{$arr[1]}();
}
}
/**
* 注册路由,加载路由文件
* @param array $map
* @return $this
*/
public function registerRoute(Array $map)
{
foreach ($map as $key => $route) {
$this->flag = $key;
$this->namespace = $route['namespace'];
// 根据route文件引入执行
require_once $route['path'];
}
return $this;
}
public function getRoutes()
{
return $this->routes;
}
}
2 . 5 event事件驱动
event事件,用于加载定义的函数 , 可用于框架初始化时要加载的配置,如加载consul 的配置信息
2 . 5 . 1
config/app.php中加载RouteServerProvider, 服务提供者启动后,EventServerProvider.php服务提供者会通过 app/event.php中定义的配置,把当定义的文件夹加载进框架中
SwoStar/Event/EventServerProvider.php : 加载event事件与绑定,解析
<?php
namespace SwoStar\Event;
use SwoStar\Config\Config;
use SwoStar\Supper\ServerPriovder;
class EventServerProvider extends ServerPriovder
{
public function boot()
{
$event = new Event();
$config = $this->app->make('config');
// 根据app/config/event.php中Listeners配置加载事件
$this->registeListenenrs($event, $config);
// 根据app/config/event.php中event配置加载
$this->registeEvents($event, $config);
$this->app->bind('event', $event);
// var_dump($this->app->make('event')->getEvents());
}
/**
* 根据app/config/event.php中Listeners配置加载
* @param Event $event
* @param Config $config
*/
public function registeListenenrs(Event $event, Config $config)
{
$listeners = $config->get('event.listeners');
foreach ($listeners as $key => $listener) {
// 根据指定的地址查询下面的PHP文件
$files = scandir($this->app->getBasePath().$listener['path']);
$data = null;
// 2. 读取文件信息
foreach ($files as $key => $file) {
if ($file === '.' || $file === '..') {
continue;
}
// 创建对象
$class = $listener['namespace'].explode(".",$file)[0];
if (class_exists($class)) {
$listener = new $class($this->app);
$event->register($listener->getName(), [$listener, 'handler']);
}
}
}
}
/**
* 根据app/config/event.php中event配置加载
* @param Event $event
* @param Config $config
*/
public function registeEvents(Event $event, Config $config)
{
$events = $config->get('event.events');
foreach ($events as $key => $class) {
if (class_exists($class)) {
$listener = new $class($this->app);
$event->register($listener->getName(), [$listener, 'handler']);
}
}
}
}
2 . 5 . 2
SwoStar/Event/Event.php : 具体的注册与解析类
<?php
namespace SwoStar\Event;
class Event
{
// 存储所有定义的事件
protected $events = [];
/**
* 注册事件方法
* @param $flag
* @param $callback
*/
public function register($flag, $callback)
{
// 统一标识 大小写
$flag = \strtolower($flag);
// 注册事件
$this->events[$flag] = [
'callback' => $callback,
];
}
/**
* 触发事件
* @param $flag .事件标识
* @param array $params .给事件传递的参数
* @return bool
*/
public function trigger($flag, $params = [])
{
// 统一标识 大小写
$flag = \strtolower($flag);
// 判断是否存在事件
if (isset($this->events[$flag])) {
($this->events[$flag]['callback'])(...$params);
return true;
}
}
/**
* 触发容器
* @return array
*/
public function getEvents()
{
return $this->events;
}
}
2 . 6 加载RPC
rpc的服务提供者,用于提供rpc中需要的tcp-service服务与client客户端
2 . 6 . 1
config/app.php 中,加载rpc的服务提供,从而驱动rpc的服务
App/Providers/RpcServerPriovder.php : 设置rpc的驱动方式,可以是config配置,这里使用consul来驱动
<?php
namespace App\Providers;
use SwoStar\Rpc\RpcServerPriovder as ServerPriovder;
/**
*
*/
class RpcServerPriovder extends ServerPriovder
{
protected $services;
/**
* 加载rpc配置信息
*/
protected function provider()
{
//这里设置了获取consul的配置,是个闭包函数,底层调用判断到$this->services是个闭包函数,则会进行实例化
$this->services = function($sname){
$services = $this->app->make('consul-agent')->health($sname)->getResult();
$address= [];
foreach ($services as $key => $value) {
$address[] = [
"host" => $value["Service"]["Address"],
"port" => $value["Service"]["Port"]
];
}
return $address;
};
}
public function boot()
{
parent::boot();
}
}
2 . 6 . 2
SwoStar/Rpc/RpcServerPriovder.php : 进行rpc服务配置信息的绑定,如rpc的服务名称,IP,端口
<?php
namespace SwoStar\Rpc;
use SwoStar\Supper\ServerPriovder;
/**
*
*/
class RpcServerPriovder extends ServerPriovder
{
protected $services;
public function boot()
{
$this->provider();
$this->app->bind('rpc-proxy', new Proxy($this->services));
}
protected function provider()
{
}
}
2 . 6 . 3
SwoStar/Rpc/Proxy.php : 根据配置的属性,返回配置的数据类型,如是返回config的配置还是返回consul的配置
<?php
namespace SwoStar\Rpc;
class Proxy
{
// 用于存储源 -- 服务源地址
protected $services;
public function __construct($services = null)
{
$this->services = $services;
}
/**
* 获取配置信息
* @param string $sname
* @return mixed
*/
public function getService($sname = '')
{
$services = $this->services($sname);
return $services[\array_rand($services, 1)];
}
/**
* 获取服务信息
* @param string $sname
* @return array|mixed|null
*/
public function services($sname = '')
{
//如果为空则获取配置文件的配置信息
if (empty($this->services)) {
return app('config')->get('service.'.$sname);
}
//这里是数组,则表示是获取配置文件中的配置信息
if (is_array($this->services)) {
return $this->services;
}
//如果调用的是consul,则为对象,则获取对象的数据
if ($this->services instanceof \Closure) {
return ($this->services)($sname);
}
}
}
2 . 6 . 4
SwoStar/Rpc/RpcClient.php : 用于调用其他服务的rpc客户端,这里的配置信息会从上面的容器中获取,请求得到相应并返回
<?php
namespace SwoStar\Rpc;
use \Swoole\Coroutine\Client;
/**
*
*/
class RpcClient
{
protected $classType ;
protected $service ;
// 通过它来调用指定的rpc服务端
protected function proxy($method, $params)
{
/*
json {
"method" : class::method,
"params" : 参数
}
*/
$data = [
'method' => $this->classType."::".$method,
'params' => $params
];
// 获取服务的配置信息
$service = app('rpc-proxy')->getService($this->service);
// 发送
return $this->send($service['host'], $service['port'], $data);
}
/**
* @param $host
* @param $port
* @param $data
* @return mixed
* @throws \Exception
*/
public function send($host, $port, $data)
{
$client = new Client(SWOOLE_SOCK_TCP);
if (!$client->connect($host, $port, 0.5))
{
throw new \Exception("连接不上服务", 500);
}
$client->send(\json_encode($data));
$ret = $client->recv(4);
$client->close();
return $ret;
}
public function __call($method, $args)
{
return $this->proxy($method, $args);
}
public static function __callStatic($method, $args)
{
return self::proxy($method, $args);
}
}
2 . 6 . 5
SwoStar/Rpc/RpcServer.php : 提供rpc的swoole-server服务,用于接收client端投递过来的数据,解析路由,执行路由绑定的控制器,并返回响应给到client端,这里有启动框架的时候,由Application.php中启动
<?php
namespace SwoStar\Rpc;
use SwoStar\Foundation\Application;
use SwoStar\Message\Response;
use SwoStar\Server\ServerBase;
class RpcServer
{
/**
* [protected description]
* @var Application
*/
protected $app;
protected $config;
/**
* [protected description]
* @var ServerBase
*/
protected $server;
protected $listen;
function __construct(Application $app, ServerBase $server)
{
$this->app = $app;
$this->server = $server;
$this->config = $this->app->make("config");
// 获取创建的swoole服务对象 调用多端口listen
$this->listen = $server->getServer()->listen($this->config->get("server.rpc.host"),$this->config->get("server.rpc.port"),$this->config->get("server.rpc.type"));
// 注册事件
$this->listen->on('receive', [$this, 'receive']);
$this->listen->on('connect', [$this, 'connect']);
$this->listen->on('close', [$this, 'close']);
// 一定要做的事情设置,swoole的配置为空或者覆盖
$this->listen->set($this->config->get("server.rpc.swoole"));
dd('tcp:\\\\'.$this->config->get("server.rpc.host").":".$this->config->get("server.rpc.port"), '启动rpc服务');
}
public function receive($ser, $fd, $from_id, $data)
{
/*
json {
"method" : class::method,
"params" : 参数
}
*/
$oper = \json_decode($data, true);
// 执行对象
$class = explode("::", $oper['method'])[0];
$class = new $class();
// 得到执行的方法
$method = explode("::", $oper['method'])[1];
// 执行
$ret = $class->{$method}(...$oper['params']);
// 返回结果
$ser->send($fd, Response::send($ret));
dd('receive'.$fd, 'rpc');
}
public function connect($ser, $fd)
{
dd('connect'.$fd, 'rpc');
}
public function close($ser, $fd)
{
dd('close'.$fd, 'rpc');
}
}
2 . 7 swoole - tcp与http的请求响应处理
统一处理tcp与http请求的Request与Response 注: 这里没进行粘包处理
2 . 7 . 1
SwoStar/Message/Http/Request.php : 请求数据处理
<?php
namespace SwoStar\Message\Http;
use Swoole\Http\Request as SwooleRequest;
class Request
{
// 记录请求方式是get还是 port
protected $method;
// 记录后续请求地址 / /dd /index/dd
protected $uriPath;
protected $swooleRequest;
public function getMethod()
{
return $this->method;
}
public function getUriPath()
{
return $this->uriPath;
}
/**
* [init description]
* @param SwooleRequest $request [description]
* @return Request [description]
*/
public static function init(SwooleRequest $request)
{
$self = new static;
$self->swooleRequest = $request;
$self->server = $request->server;
$self->method = $request->server['request_method'] ?? '';
$self->uriPath = $request->server['request_uri'] ?? '';
return $self;
}
}
2 . 7 . 2
SwoStar/Message/Response.php : 返回响应处理,如果需要做粘包处理则在此处解决
<?php
namespace SwoStar\Message;
/**
*
*/
class Response
{
public static function send($message)
{
if (\is_array($message)) {
return json_encode($message);
} else if (\is_string($message)) {
return $message;
} else {
return $message;
}
}
}
2 . 8 consul服务加载
创建consul的服务,对接consul,在框架启动的时候加载配置信息注册到consul,rpc服务调用的时候获取consul存储的配置信息,使用consul的好处是consul可以监听服务,返回存活状态的服务,如果不考虑心跳,可以使用redis或者config配置来存储配置信息
config/app.php 中加载ConsulServerPriovder,进行consul驱动绑定,并在event事件中进去服务注册
2 . 8 . 1
SwoStar/Supper/ServerPriovder/ConsulServerPriovder.php : 进行consul的请求类的绑定
<?php
namespace SwoStar\Consul;
use SwoStar\Supper\ServerPriovder;
/**
*
*/
class ConsulServerPriovder extends ServerPriovder
{
/**
* 加载consul驱动
*/
public function boot()
{
$config = $this->app->make("config");
$this->app->bind("consul-agent",
new Agent(
new Consul($config->get('consul.app.host'), $config->get('consul.app.port'))
)
);
}
}
2 . 8 . 2
SwoStar/Consul/Consul.php 基于携程的swoole-http请求
<?php
namespace SwoStar\Consul;
use Swoole\Coroutine\Http\Client;
class Consul
{
protected $host;
protected $port;
public function __construct($host , $port)
{
$this->host = $host;
$this->port = $port;
}
public function get(string $url = null, array $options = [])
{
return $this->request('GET', $url, $options);
}
public function delete(string $url, array $options = [])
{
return $this->request('DELETE', $url, $options);
}
public function put(string $url, array $options = [])
{
return $this->request('PUT', $url, $options);
}
public function patch(string $url, array $options = [])
{
return $this->request('PATCH', $url, $options);
}
public function post(string $url, array $options = [])
{
return $this->request('POST', $url, $options);
}
public function options(string $url, array $options = [])
{
return $this->request('OPTIONS', $url, $options);
}
/**
* 携程执行请求
* @param $method
* @param $uri
* @param $options
* @return Response
*/
private function request($method, $uri, $options)
{
$client = new Client($this->host, $this->port);
// get , DELETE
$client->setMethod($method);
if (!empty($options)) {
$client->setData(json_encode($options['body']));
}
$client->execute($uri);
$headers = $client->headers;
$statusCode = $client->statusCode;
$body = $client->body;
$client->close();
return Response::new($headers, $body, $statusCode);
}
}
2 . 8 . 3
SwoStar/Consul/Response.php 请求与响应
<?php
namespace SwoStar\Consul;
class Response
{
/**
* @var array
*/
private $headers;
/**
* @var string
*/
private $body;
/**
* @var int
*/
private $status;
/**
* @param array $headers
* @param string $body
* @param $status
* @return Response
*/
public static function new(array $headers, string $body, $status = 200): self
{
$self = app(Response::class);
$self->body = $body;
$self->status = $status;
$self->headers = $headers;
return $self;
}
/**
* @return array
*/
public function getHeaders(): array
{
return $this->headers;
}
/**
* @return string
*/
public function getBody(): string
{
return $this->body;
}
/**
* @return int
*/
public function getStatusCode(): int
{
return $this->status;
}
/**
* @return array|mixed
*/
public function getResult()
{
if (empty($this->body)) {
return $this->body;
}
return \json_decode($this->body, true);
}
}
2 . 8 . 4
SwoStar/Consul/Agent.php 请求consul
<?php
namespace SwoStar\Consul;
class Agent
{
/**
* @var Consul
*/
protected $consul;
public function __construct($consul)
{
$this->consul = $consul;
}
/**
* 获取所有的服务信息
* @return Response
*/
public function services()
{
return $this->consul->get('/v1/agent/services');
}
/**
* 从consul中获取当前健康的服务
* @method health
* @param [type] $sname [description]
* @return [type] [description]
*/
public function health($sname)
{
return $this->consul->get('/v1/health/service/'.$sname."?passing=true");
}
/**
* 注册服务,在event事件中监听启动
* @param array $service
* @return Response
*/
public function registerService(array $service)
{
$params = [
'body' => $service,
];
return $this->consul->put('/v1/agent/service/register', $params);
}
/**
* 注销服务
* @param string $serviceId
* @return Response
*/
public function deregisterService(string $serviceId)
{
return $this->consul->put('/v1/agent/service/deregister/' . $serviceId);
}
}
2 . 9 基于swoole提供http服务
在加载完框架后,就开始启动swoole-http服务,启动后,前面加载的框架会被加载进电脑的内存中进行常驻,当有请求到onRequest的时候,就会解析路由,请求到对应的控制器中,完成业务逻辑后返回响应
2 . 9 . 1
SwoStar/Server/Http/Http.php : 提供swoole的http服务,加载框架进内存中(启动http服务,前面加载的框架就会进入内存中),获取http响应,解析路由
<?php
namespace SwoStar\Server\Http;
use SwoStar\Console\Input;
use SwoStar\Message\Response;
use SwoStar\Server\ServerBase;
use SwoStar\Message\Http\Request;
class Server extends ServerBase
{
/**
* 因为不同的服务构建不一样
*/
protected function createServer(){
$this->server = new \Swoole\Http\Server($this->host, $this->port);
Input::info("http:\\\\".swoole_get_local_ip()['eth0'].":".$this->port, "访问地址");
}
/**
* 因为不同的服务构建不一样
*/
protected function initServerConfig(){
$this->port = $this->app->make("config")->get("server.http.port");
$this->host = $this->app->make("config")->get("server.http.host");
}
/**
* 因为不同的服务构建不一样
*/
protected function initEvent(){
$this->setEvent('sub', [
'request' => 'onRequest'
]);
}
/**
* @param $request
* @param $response
*/
public function onRequest($request, $response)
{
if ($request->server['path_info'] == '/favicon.ico' || $request->server['request_uri'] == '/favicon.ico') {
$response->end();
return;
}
// 获取请求对象
$httpRequest = Request::init($request);
// 解析路由并响应请求
$return = $this->app->make('route')->match($httpRequest->getUriPath(), "http", $httpRequest->getMethod());
$response->end(Response::send($return));
}
}
2 . 9 . 2
SwoStar/Server/ServerBase.php : 完成swoole-http 的其他回调事件,如框架启动时要触发的事件,注册服务到consul等
<?php
namespace SwoStar\Server;
use SwoStar\Foundation\Application;
abstract class ServerBase
{
/**
* @var SwoStar\Foundation\Application
*/
protected $app;
/**
* @var Swoole\Server
*/
protected $server;
protected $host = '0.0.0.0';
protected $port = 9500;
protected $serverConfig = [
'task_worker_num' => 0,
];
protected $serverEvent = [
"server" => [ // 有swoole的本身的事件 -- 是在swoole的整体生命周期
'start' => 'onStart',
'Shutdown' => "onShutdown"
],
"sub" => [], // http - websocket 是记录明确swoole服务独有的事件
"ext" => [], // 根据用户扩展task事件
];
function __construct(Application $app)
{
$this->app = $app;
// 初识化服务的设置
$this->initServerConfig();
// 创建服务
$this->createServer();
// 初识事件
$this->initEvent();
// 设置回调函数
$this->setSwooleEvent();
}
/**
* 因为不同的服务构建不一样
*/
abstract protected function initServerConfig();
/**
* 因为不同的服务构建不一样
*/
abstract protected function createServer();
/**
* 因为不同的服务构建不一样
*/
abstract protected function initEvent();
public function onStart($server) {
$this->app->make('event')->trigger("swoole:start", [$this, $server]);
}
public function onShutdown($server) {
$this->app->make('event')->trigger("swoole:stop", [$this, $server]);
}
/**
* 设置swoole的回调事件
*/
protected function setSwooleEvent()
{
foreach ($this->serverEvent as $type => $events) {
foreach ($events as $event => $func) {
$this->server->on($event, [$this, $func]);
}
}
}
public function start()
{
$this->server->set($this->serverConfig);
$this->server->start();
}
/**
* @param array
*
* @return static
*/
public function setEvent($type, $event)
{
// 暂时不支持直接设置系统的回调事件
if ($type == "server") {
return;
}
$this->serverEvent[$type] = $event;
}
public function getServer()
{
return $this->server;
}
}
2 . 10 测试
到这里,一个简单的基本的框架核心就搭建好了,这里在config/server.php文件进行配置,就可以启动服务了,如果这里选择了配置了consul,则需要先把consul的服务启动好。
2 . 10 . 1 运行服务:
php bin/swostar http:start
consul中的服务注册
2 . 10 . 2 rpc服务测试
复制当先项目,更改为另一个项目,这里用 activities 项目,部署到另一台机器中,如下
启动服务:
consul服务
注意 : 这里如果用的docker容器,则需要进行nginx的代理,由于这里用的是docker,所以需要用到nginx给http和tcp进行代理,需要在两台机器上进行配置,这里只那一台服务器进行演示
2 . 10 . 3 nginx代理http与tcp
代理http
server {
listen 9506;
listen [::]:9506;
#server_name localhost;
#root /docker/www/work/18/activities;
#index index.php index.html;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://172.17.0.3:9506;
}
}
代理tcp
upstream swoft-ssh {
hash $remote_addr consistent;
server 172.17.0.3:9508 weight=5 max_fails=3 fail_timeout=30s;
}
server {
#监听端口
listen 9508;
proxy_connect_timeout 10s;
#设置客户端和代理服务之间的超时时间,如果5分钟内没操作将自动断开。
proxy_timeout 300s;
proxy_pass swoft-ssh;
}
配置好后,基本就可以请求到框架了
2 . 10 . 4 在goods服务中添加rpc服务端逻辑
App/Rpc/Service/DemoService.php : 提供服务
<?php
namespace App\Rpc\Service;
class DemoService
{
public function getList()
{
return ["data" => '这里是goods服务提供的数据'];
}
public function getUser()
{
return ["data" => 'getUser'];
}
}
2 . 10 . 5 在activities服务的控制器中,远程rpc调用
App/Http/Controller/IndexController.php :
<?php
namespace App\Http\Controller;
use App\Rpc\Client\DemoClient;
class IndexController
{
public function demo()
{
$data = app('consul-agent')->health('swostar');
return ['msg'=> '操作成功','status' => 200, 'data' => [$data]];
}
public function rpc()
{
//远程调用goods服务上的DemoClient控制器的getList方法
$out = (new DemoClient)->getList();
return ['msg'=> '操作成功','status' => 200, 'data' => json_decode($out)];
}
}
添加DemoClient的swoole客户端代码
App/Rpc/Client/DemoClient.php
<?php
namespace App\Rpc\Client;
use SwoStar\Rpc\RpcClient;
class DemoClient extends RpcClient
{
//goods服务中DemoService类
protected $classType = \App\Rpc\Service\DemoService::class;
//这里goods既是consul注册服务时的NAME
protected $service = "goods";
}
添加路由文件:
route/http.php
<?php
use SwoStar\Routes\Route;
Route::get('index', function(){
return "this is route index() test";
});
Route::get('/index/rpc', 'IndexController@rpc');
请求返回: