/src/Phunk/Handler/BuiltinWebServer.php

https://github.com/yuya-takeyama/Phunk · PHP · 147 lines · 105 code · 12 blank · 30 comment · 15 complexity · 1948d672089d12f382a04c43b2c3333f MD5 · raw file

  1. <?php
  2. namespace Phunk\Handler;
  3. class BuiltinWebServer implements \Phunk\Handler
  4. {
  5. /**
  6. * @internal
  7. * @var string
  8. */
  9. public $_temporary_file;
  10. /**
  11. * @internal
  12. * @var resource
  13. */
  14. public $_process;
  15. /**
  16. * @internal
  17. * @var array
  18. */
  19. public $_pipes;
  20. /**
  21. * @param callable $app
  22. * @throws \Exception
  23. * @return void
  24. */
  25. function run(callable $app)
  26. {
  27. $this->_build_server_process();
  28. print "server started at http://localhost:1985/\n";
  29. print "when stop the server, use 'stop' command.\n";
  30. $write = array();
  31. $except = array();
  32. while (1) {
  33. if (false === $this->_check_process()) {
  34. fputs(STDERR, "server stopped.\n");
  35. break;
  36. }
  37. $read = array(STDIN, $this->_pipes[1], $this->_pipes[2]);
  38. $changed = stream_select($read, $write, $except, 0, 200000);
  39. if (false === $changed) {
  40. throw new \Exception();
  41. } elseif ($changed > 0) {
  42. foreach ($read as $r) {
  43. if (STDIN === $r) {
  44. $in = strtolower(trim(fread($r, 8192)));
  45. if ('stop' === $in) {
  46. break 2;
  47. }
  48. } elseif ($this->_pipes[1] === $r) {
  49. print fread($r, 8192);
  50. } else if ($this->_pipes[2] === $r) {
  51. fputs(STDERR, fread($r, 8192));
  52. }
  53. }
  54. }
  55. }
  56. }
  57. /**
  58. * @internal
  59. * @throws \Exception
  60. * @return void
  61. */
  62. function _build_server_process()
  63. {
  64. $this->_build_temporary_file();
  65. register_shutdown_function(
  66. function()
  67. {
  68. if ($this->_process) {
  69. foreach ($this->_pipes as $pipe) {
  70. fclose($pipe);
  71. }
  72. proc_terminate($this->_process);
  73. }
  74. unlink($this->_temporary_file);
  75. }
  76. );
  77. if (array_key_exists('_', $_SERVER)) {
  78. $php = $_SERVER['_'];
  79. } else {
  80. $php = PHP_BINDIR . DIRECTORY_SEPARATOR . 'php';
  81. }
  82. $descriptor_spec = array(
  83. 0 => array("pipe", "r"),
  84. 1 => array("pipe", "w"),
  85. 2 => array("pipe", "w"),
  86. );
  87. $command = "$php -S=localhost:1985 {$this->_temporary_file}";
  88. $this->_process = proc_open($command, $descriptor_spec, $this->_pipes);
  89. if (false === $this->_process) {
  90. throw new \Exception("command failed: $command");
  91. }
  92. stream_set_blocking(STDIN, 0);
  93. stream_set_blocking($this->_pipes[1], 0);
  94. stream_set_blocking($this->_pipes[2], 0);
  95. }
  96. /**
  97. * @internal
  98. * @return void
  99. */
  100. function _build_temporary_file()
  101. {
  102. global $argv;
  103. $temporary_dir = sys_get_temp_dir();
  104. $this->_temporary_file = tempnam($temporary_dir, 'phunk');
  105. $include_path = get_include_path();
  106. $phunki = realpath($argv[0]);
  107. $trace = debug_backtrace(false, 4);
  108. if (isset($trace[3]) &&
  109. preg_match('/' . preg_quote(DIRECTORY_SEPARATOR, '/') . 'phunk_up\\.php$/', $trace[3]['file'])
  110. ) {
  111. $code = <<<CODE
  112. <?php
  113. set_include_path('$include_path');
  114. require_once 'Autoloader/Simple.php';
  115. spl_autoload_register(array('Autoloader_Simple', 'load'));
  116. Phunk\Util::phunk_up('$phunki');
  117. CODE;
  118. } else {
  119. $code = "<?php require '$phunki';";
  120. }
  121. file_put_contents($this->_temporary_file, $code);
  122. }
  123. /**
  124. * @internal
  125. * @return bool
  126. */
  127. function _check_process()
  128. {
  129. $status = proc_get_status($this->_process);
  130. if ($status && $status['running']) {
  131. return true;
  132. }
  133. return $this->_process = false;
  134. }
  135. }