1
|
|
|
<?php |
2
|
|
|
namespace Fructify\Reload\Protocol; |
3
|
|
|
|
4
|
|
|
use Fructify\Reload\Application\ServerApplication; |
5
|
|
|
use React\Socket\Server as SocketServer; |
6
|
|
|
use React\Socket\Connection as SocketConnection; |
7
|
|
|
use Symfony\Component\HttpFoundation\Request; |
8
|
|
|
use Fructify\Reload\Response\Response; |
9
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
10
|
|
|
|
11
|
|
|
class HttpProtocol |
12
|
|
|
{ |
13
|
|
|
protected $app; |
14
|
|
|
|
15
|
|
|
public function __construct(SocketServer $socket, ServerApplication $app) |
16
|
|
|
{ |
17
|
|
|
$this->app = $app; |
18
|
|
|
$this->initEvent($socket); |
19
|
|
|
} |
20
|
|
|
|
21
|
|
|
protected function initEvent(SocketServer $socket) |
22
|
|
|
{ |
23
|
|
|
$socket->on('connection', function(SocketConnection $conn){ |
24
|
|
|
$this->onConnect($conn); |
25
|
|
|
}); |
26
|
|
|
} |
27
|
|
|
|
28
|
|
|
protected function onConnect(SocketConnection $conn) |
29
|
|
|
{ |
30
|
|
|
$conn->on('data', function($data) use($conn){ |
31
|
|
|
$this->onData($conn, $data); |
32
|
|
|
}); |
33
|
|
|
} |
34
|
|
|
|
35
|
|
|
protected function onData(SocketConnection $conn, $data) |
36
|
|
|
{ |
37
|
|
|
$request = $this->doHttpHandshake($data); |
38
|
|
|
$this->handleRequest($conn, $request); |
|
|
|
|
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
protected function handleRequest(SocketConnection $conn, Request $request) |
42
|
|
|
{ |
43
|
|
|
switch($request->getPathInfo()){ |
44
|
|
|
case '/livereload': |
45
|
|
|
$this->initWebSocket($conn, $request); |
46
|
|
|
break; |
47
|
|
|
case '/livereload.js': |
48
|
|
|
$this->serveFile($conn, __DIR__.'/../../web/js/livereload.js'); |
49
|
|
|
break; |
50
|
|
|
case '/changed': |
51
|
|
|
$this->notifyChanged($conn, $request); |
52
|
|
|
break; |
53
|
|
|
default: |
54
|
|
|
$this->serve404Error($conn); |
55
|
|
|
} |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
protected function initWebSocket(SocketConnection $conn, Request $request) |
59
|
|
|
{ |
60
|
|
|
$conn->removeAllListeners('data'); |
61
|
|
|
return new WebSocketProtocol($conn, $this->app, $request); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
protected function getRequestChangedFiles(Request $request) |
65
|
|
|
{ |
66
|
|
|
if(($files = $request->query->get('files')) != null){ |
67
|
|
|
return explode(',', $files); |
68
|
|
|
} |
69
|
|
|
$requestJson = json_decode($request->getContent(), true); |
70
|
|
|
return isset($requestJson['files'])?(is_array($requestJson['files'])?$requestJson['files']:[$requestJson['files']]):[]; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
protected function notifyChanged(SocketConnection $conn, Request $request) |
74
|
|
|
{ |
75
|
|
|
foreach($this->getRequestChangedFiles($request) as $file){ |
76
|
|
|
$this->app->getOutput()->writeln(strftime('%T')." - info - Receive request reload $file", OutputInterface::VERBOSITY_VERBOSE); |
77
|
|
|
$this->app->reloadFile($file); |
78
|
|
|
} |
79
|
|
|
$response = new Response(json_encode(array('status' => true))); |
80
|
|
|
$conn->write($response); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
protected function serveFile(SocketConnection $conn, $file) |
84
|
|
|
{ |
85
|
|
|
if(($path = realpath($file)) === null){ |
86
|
|
|
return ; |
87
|
|
|
} |
88
|
|
|
$content = file_get_contents($file); |
89
|
|
|
$response = new Response($content); |
90
|
|
|
$response->setContentType('text/plain', 'utf-8'); |
91
|
|
|
$conn->write($response); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
protected function serve404Error(SocketConnection $conn) |
95
|
|
|
{ |
96
|
|
|
$response = new Response('file not found.', Response::HTTP_NOT_FOUND); |
97
|
|
|
$conn->write($response); |
98
|
|
|
$conn->end(); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
protected function doHttpHandshake($data) |
102
|
|
|
{ |
103
|
|
|
$pos = strpos($data, "\r\n\r\n"); |
104
|
|
|
if($pos === false){ |
105
|
|
|
return false; |
106
|
|
|
} |
107
|
|
|
$body = substr($data, $pos + 4); |
108
|
|
|
$rawHeaders = explode("\r\n", substr($data, 0, $pos)); |
109
|
|
|
$requestLine = $this->parseRequest(array_shift($rawHeaders)); |
110
|
|
|
$headers = $this->parseHeaders($rawHeaders); |
111
|
|
|
return Request::create($requestLine['uri'], $requestLine['method'], array(), array(), array(), $headers, $body); |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
protected function parseRequest($rawRequest) |
115
|
|
|
{ |
116
|
|
|
return array_combine(array('method', 'uri', 'protocol'), explode(' ', $rawRequest)); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
protected function parseHeaders($rawHeaders) |
120
|
|
|
{ |
121
|
|
|
$headers = array(); |
122
|
|
|
foreach($rawHeaders as $headerLine){ |
123
|
|
|
if(($pos = strpos($headerLine, ':')) === false){ |
124
|
|
|
continue; |
125
|
|
|
} |
126
|
|
|
$headers['HTTP_'.str_replace('-', '_', trim(strtoupper(substr($headerLine, 0, $pos))))] = trim(substr($headerLine, $pos + 1)); |
127
|
|
|
} |
128
|
|
|
return $headers; |
129
|
|
|
} |
130
|
|
|
} |
131
|
|
|
|
This check looks for type mismatches where the missing type is
false
. This is usually indicative of an error condtion.Consider the follow example
This function either returns a new
DateTime
object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returnedfalse
before passing on the value to another function or method that may not be able to handle afalse
.