swoole与php框架结合实现http服务

swoole是一款高性能的网络服务器,现在市面上大多数php的web程序是LNMP架构或LAMP(LNAMP),即通过nginx将PHP相关的请求转发到PHP(php-fpm)服务器上。使用swoole以后,它会接管php-fpm的控制权,因此就需要使用swoole的http server来处理相关的请求与响应,它和其他框架的结合主要是使用swoole的http server中的request和response对象来接受请求并做出响应,需要与现有框架的路由分发进行结合。

先用一个自定义的框架与swoole结合,主要说明原理

  • 建立http server,将请求事件(onRequest)转发到路由分发模块(Dispatcher)进行处理
  • 路由分发模块解析请求的url,进行分发处理
    项目目录结构:

目录说明:

  • app是应用主目录,包括控制器等文件
  • app.php为应用类,处理文件加载等
  • dispatcher.php为路由分发器,将请求对应到各个控制器文件上,并做出响应
  • server.php为http server处理文件

1、建立一个应用类,主要是加载文件等功能

  1. <?php
  2. class App
  3. {
  4. //文件集合
  5. private static $_fileMaps = array();
  6. /**
  7. * 加载文件
  8. * @param string $file 文件名
  9. * @return boolean
  10. */
  11. public static function load($file)
  12. {
  13. if (!self::inFileMaps($file)) {
  14. if (is_file($file)) {
  15. require($file);
  16. self::$_fileMaps[$file] = true;
  17. return true;
  18. } else {
  19. return false;
  20. }
  21. }
  22. return true;
  23. }
  24. /**
  25. * 判断文件是否已经加载
  26. * @param string $file 文件名
  27. * @return boolean
  28. */
  29. public static function inFileMaps($file)
  30. {
  31. $files = array_keys(self::$_fileMaps);
  32. if (in_array($file, $files)) {
  33. return true;
  34. }
  35. return false;
  36. }
  37. }

2、建立http server处理请求

  1. <?php
  2. require __DIR__.'/app.php';
  3. class HttpServer
  4. {
  5. //http server 对象
  6. private $_server;
  7. /**
  8. * 初始化http server对象
  9. *
  10. **/
  11. public function __construct()
  12. {
  13. $this->_server = new swoole\http\server('0.0.0.0', '9501');
  14. }
  15. /**
  16. * 启动http server
  17. *
  18. **/
  19. public function run()
  20. {
  21. //http server设置
  22. $this->_server->set(
  23. array(
  24. 'worker_num'=>2
  25. )
  26. );
  27. //事件绑定
  28. $this->_server->on('request', array($this, 'onRequest'));
  29. $this->_server->on('close', array($this, 'onClose'));
  30. //启动http server
  31. $this->_server->start();
  32. }
  33. /**
  34. * 请求处理函数
  35. * @param swoole\http\request $req 请求对象(包含header,server等属性)
  36. * @param swoole\http\response $res 响应对象
  37. **/
  38. public function onRequest(swoole\http\Request $req, swoole\http\Response $res)
  39. {
  40. App::load(__DIR__.'/dispatcher.php');
  41. //将请求和响应对象传入到dispatcher中
  42. Dispatcher::getInstance()->dispatch($req, $res);
  43. }
  44. public function onClose()
  45. {
  46. echo 'closing.....';
  47. }
  48. }
  49. $http = new HttpServer();
  50. $http->run();

3、路由分发与响应处理

  1. <?php
  2. class Dispatcher
  3. {
  4. private static $_instance;
  5. private $module;
  6. private $controller;
  7. private $action;
  8. private $params;
  9. private function __construct()
  10. {
  11. }
  12. public static function getInstance()
  13. {
  14. if (!self::$_instance || !self::$_instance instanceof self) {
  15. self::$_instance = new self();
  16. }
  17. return self::$_instance;
  18. }
  19. /**
  20. * 路由分发分派方法
  21. * @param swoole\http\request $req 请求对象
  22. * @param swoole\http\response $res 响应对象
  23. **/
  24. public function dispatch($req, $res)
  25. {
  26. $pathinfo = $req->server['path_info'];
  27. //将/favicon.ico过滤掉(当然这个可以将它交给nginx)
  28. if ($pathinfo == '/favicon.ico') {
  29. $res->status(404);
  30. $res->end('Page not found');
  31. }
  32. //解析请求参数
  33. if (isset($req->server['query_string'])) {
  34. parse_str($req->server['query_string'], $this->params);
  35. }
  36. //分析请求url(path info)
  37. $pathArr = array_values(array_filter(explode('/', $pathinfo)));
  38. //将index.php等这些入口文件过滤掉
  39. if (strpos($pathArr[0], '.php') > 0) {
  40. array_shift($pathArr);
  41. }
  42. $count = count($pathArr);
  43. //分解到模块、控制器、操作上
  44. if ($count == 3) {
  45. $this->module = $pathArr[0];
  46. $this->controller = $pathArr[1];
  47. $this->action = $pathArr[2];
  48. } elseif($count == 2) {
  49. $this->controller = $pathArr[0];
  50. $this->action = $pathArr[1];
  51. } else {
  52. $this->controller = isset($pathArr[0]) ? $pathArr[0] : 'index';
  53. $this->action = 'index';
  54. }
  55. //获取控制器文件路径
  56. $path = $this->getFilePath();
  57. //加载控制器文件
  58. $result = App::load($path);
  59. if (!$result) {
  60. $res->write('the file '.$path.' not found');
  61. } else {
  62. $className = ucfirst($this->controller);
  63. if (!class_exists($className)) {
  64. $res->write('the class '.$className.' not found in file '.$path);
  65. } else {
  66. $obj = new $className();
  67. if (!method_exists($obj, $this->action)) {
  68. $res->write('the method '.$this->action.' does not exists in class '.$className);
  69. } else {
  70. $obj->params = $this->params;
  71. ob_start();
  72. $action = $this->action;
  73. $obj->$action();
  74. //获取方法输出的内容(如果方法只是返回可以不用这种方式)
  75. $body = ob_get_contents();
  76. ob_clean();
  77. //响应请求
  78. $res->write($body);
  79. }
  80. }
  81. }
  82. }
  83. protected function getFilePath()
  84. {
  85. //控制器文件基准目录(可以通过配置来)
  86. $path = __DIR__.'/app/controller';
  87. if ($this->module) {
  88. $path .= '/'.$this->module;
  89. }
  90. if ($this->controller) {
  91. $path .= '/'.strtolower($this->controller).'.php';
  92. }else {
  93. return false;
  94. }
  95. return $path;
  96. }
  97. public function getModule()
  98. {
  99. return $this->module;
  100. }
  101. public function getController()
  102. {
  103. return $this->controller;
  104. }
  105. public function getAction()
  106. {
  107. return $this->action;
  108. }
  109. public function getParams()
  110. {
  111. return $this->params;
  112. }
  113. }

这里以一个自定义的框架来说明,主要说明原理,与其他框架结合步骤类似,把请求和响应对象,作为参数传给框架的路由分发器(调用路由分发器)