[a-f0-9]{7})$/', $tagInfo, $matches)) {
266 | return sprintf('%s@%s', $matches['tag'], $matches['hash']);
267 | } elseif ($tagInfo) {
268 | return $tagInfo;
269 | }
270 | }
271 | return $defaultVersion;
272 | }
273 |
274 | /**
275 | * error.
276 | *
277 | * @param $message
278 | * @param int $exitCode
279 | */
280 | function smproxy_error($message, $exitCode = 0)
281 | {
282 | $parts = explode(':', $message, 2);
283 |
284 | $parts[0] = strtoupper($parts[0]);
285 |
286 | $prefixExists = in_array($parts[0], [
287 | 'ERROR', 'WARNING', 'NOTICE',
288 | ]);
289 |
290 | if ($prefixExists) {
291 | $message = $parts[0] . ': ' . trim($parts[1]);
292 | } else {
293 | $message = 'ERROR: ' . $message;
294 | }
295 |
296 | error_log($message);
297 |
298 | if (!$prefixExists || 'ERROR' == $parts[0]) {
299 | exit($exitCode);
300 | }
301 | }
302 |
303 | /**
304 | * 获取包长配置
305 | *
306 | * @return array
307 | */
308 | function packageLengthSetting()
309 | {
310 | $package_length_func = function ($data) {
311 | if (strlen($data) < 4) {
312 | return 0;
313 | }
314 | $length = ord($data[0]) | (ord($data[1]) << 8) | (ord($data[2]) << 16);
315 | if ($length <= 0) {
316 | return -1;
317 | }
318 | return $length + 4;
319 | };
320 | return [
321 | 'open_length_check' => true,
322 | 'package_length_func' => $package_length_func,
323 | ];
324 | }
325 |
326 | /**
327 | * 获取包长
328 | *
329 | * @param string $data
330 | * @param int $step
331 | * @param int $offset
332 | *
333 | * @return int
334 | */
335 | function getPackageLength(string $data, int $step, int $offset)
336 | {
337 | $i = ord($data[$step]);
338 | $i |= ord($data[$step + 1]) << 8;
339 | $i |= ord($data[$step + 2]) << 16;
340 | if ($offset >= 4) {
341 | $i |= ord($data[$step + 3]) << 24;
342 | }
343 |
344 | return $i + $offset;
345 | }
346 |
347 | /**
348 | * 处理异常
349 | *
350 | * @param int $errno
351 | * @param string $errstr
352 | * @param string $errfile
353 | * @param int $errline
354 | */
355 | function _error_handler(int $errno, string $errstr, string $errfile, int $errline)
356 | {
357 | $errCode = strlen($errstr) > 3 ? substr($errstr, -4, 3) : 0;
358 | $errMethod = explode(': ', $errstr)[0] ?? '';
359 | if (strrpos($errMethod, 'Swoole\Coroutine\Client') === false && $errCode != '110' && $errCode != '111'
360 | && !(basename($errfile) == 'ServerParse.php' && $errno == E_NOTICE)) {
361 | $system_log = Log::getLogger('system');
362 | $message = sprintf('%s (%s:%s)', $errstr, $errfile, $errline);
363 | $errLevel = $errno ? (array_search($errno + 1, Log::$levels) ?: 'error') : 'error';
364 | $system_log->$errLevel($message);
365 | if (CONFIG['server']['swoole']['daemonize'] != true) {
366 | echo '[' . ucfirst($errLevel) . '] ', trim($message), PHP_EOL;
367 | }
368 | }
369 | }
370 |
371 | function startsWith($haystack, $needle)
372 | {
373 | // search backwards starting from haystack length characters from the end
374 | return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== false;
375 | }
376 |
377 | function endsWith($haystack, $needle)
378 | {
379 | // search forward starting from end minus needle length characters
380 | return $needle === "" || (($temp = strlen($haystack) - strlen($needle)) >= 0 && strpos($haystack, $needle, $temp) !== false);
381 | }
382 |
--------------------------------------------------------------------------------
/src/Log/Log.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 2018/11/12
5 | * Time: 上午9:56.
6 | */
7 |
8 | namespace SMProxy\Log;
9 |
10 | use Swoole\Coroutine;
11 | use Psr\Log\AbstractLogger;
12 | use Psr\Log\LogLevel;
13 | use Psr\Log\InvalidArgumentException;
14 |
15 | /**
16 | * 日志类.
17 | */
18 | class Log extends AbstractLogger
19 | {
20 | // 日志根目录
21 | private $logPath = '.';
22 |
23 | // 日志文件
24 | private $logFile = 'system.log';
25 |
26 | // 日志自定义目录
27 | private $format = 'Y/m/d';
28 |
29 | // 日志标签
30 | private $tag = 'system';
31 |
32 | // 总配置设定
33 | private static $CONFIG = [];
34 |
35 | public static $open = true;
36 |
37 | public static $levels = [
38 | LogLevel::DEBUG => 0,
39 | LogLevel::INFO => 1,
40 | LogLevel::NOTICE => 2,
41 | LogLevel::WARNING => 3,
42 | LogLevel::ERROR => 4,
43 | LogLevel::CRITICAL => 5,
44 | LogLevel::ALERT => 6,
45 | LogLevel::EMERGENCY => 7,
46 | ];
47 |
48 | private $minLevelIndex;
49 |
50 | /**
51 | * Log constructor.
52 | *
53 | * @param array $config
54 | * @param null $minLevel
55 | */
56 | public function __construct(array $config, $minLevel = null)
57 | {
58 | // 日志根目录
59 | if (isset($config['log_path'])) {
60 | $this->logPath = $config['log_path'];
61 | }
62 |
63 | // 日志文件
64 | if (isset($config['log_file'])) {
65 | $this->logFile = $config['log_file'];
66 | }
67 |
68 | // 日志自定义目录
69 | if (isset($config['format'])) {
70 | $this->format = $config['format'];
71 | }
72 |
73 | // 日志标签
74 | if (isset($config['tag'])) {
75 | $this->tag = $config['tag'];
76 | }
77 |
78 | if (null === $minLevel) {
79 | $minLevel = LogLevel::WARNING;
80 |
81 | if (isset($_ENV['SHELL_VERBOSITY']) || isset($_SERVER['SHELL_VERBOSITY'])) {
82 | switch ((int) (isset($_ENV['SHELL_VERBOSITY']) ? $_ENV['SHELL_VERBOSITY'] : $_SERVER['SHELL_VERBOSITY'])) {
83 | case -1:
84 | $minLevel = LogLevel::ERROR;
85 | break;
86 | case 1:
87 | $minLevel = LogLevel::NOTICE;
88 | break;
89 | case 2:
90 | $minLevel = LogLevel::INFO;
91 | break;
92 | case 3:
93 | $minLevel = LogLevel::DEBUG;
94 | break;
95 | }
96 | }
97 | }
98 |
99 | if (!isset(self::$levels[$minLevel])) {
100 | throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $minLevel));
101 | }
102 |
103 | $this->minLevelIndex = self::$levels[$minLevel];
104 | }
105 |
106 | /**
107 | * 添加日志。
108 | *
109 | * @param mixed $level
110 | * @param string $message
111 | * @param array $context
112 | */
113 | public function log($level, $message, array $context = [])
114 | {
115 | if (self::$open) {
116 | if (!isset(self::$levels[$level])) {
117 | throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level));
118 | }
119 |
120 | if (self::$levels[$level] < $this->minLevelIndex) {
121 | return;
122 | }
123 |
124 | $log_data = $this ->format($level, $message, $context);
125 |
126 | // 获取日志文件
127 | $log_file = $this->getLogFile();
128 |
129 | // 创建日志目录
130 | $is_create = $this->createLogPath(dirname($log_file));
131 | // 写入日志文件
132 | if ($is_create) {
133 | if (Coroutine::getuid() > 0) {
134 | // 协程写
135 | $this->coWrite($log_file, $log_data);
136 | } else {
137 | $this->syncWrite($log_file, $log_data);
138 | }
139 | }
140 | }
141 | }
142 |
143 | /**
144 | * 格式化输出信息。
145 | *
146 | * @param string $level
147 | * @param string $message
148 | * @param array $context
149 | *
150 | * @return string
151 | */
152 | private function format(string $level, string $message, array $context): string
153 | {
154 | if (false !== strpos($message, '{')) {
155 | $replacements = [];
156 | foreach ($context as $key => $val) {
157 | if (null === $val || is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) {
158 | $replacements["{{$key}}"] = $val;
159 | } elseif ($val instanceof \DateTimeInterface) {
160 | $replacements["{{$key}}"] = $val->format('Y-m-d H:i:s');
161 | } elseif (\is_object($val)) {
162 | $replacements["{{$key}}"] = '[object ' . \get_class($val) . ']';
163 | } else {
164 | $replacements["{{$key}}"] = '[' . \gettype($val) . ']';
165 | }
166 | }
167 |
168 | $message = strtr($message, $replacements);
169 | }
170 |
171 | return sprintf('%s [%s] %s', date('Y-m-d H:i:s'), $level, $message) . \PHP_EOL;
172 | }
173 |
174 | /**
175 | * 获取日志类对象。
176 | *
177 | * @param string $tag
178 | *
179 | * @return Log
180 | */
181 | public static function getLogger(string $tag = 'system')
182 | {
183 | if (!is_array(self::$CONFIG) || empty(self::$CONFIG)) {
184 | self::$CONFIG = CONFIG['server']['logs']['config'];
185 | self::$open = CONFIG['server']['logs']['open'];
186 | }
187 |
188 | // 根据tag从总配置中获取对应设定,如不存在使用system设定
189 | $config = isset(self::$CONFIG[$tag]) ? self::$CONFIG[$tag] :
190 | (isset(self::$CONFIG['system']) ? self::$CONFIG['system'] : []);
191 |
192 | // 设置标签
193 | $config['tag'] = '' != $tag && 'system' != $tag ? $tag : '-';
194 |
195 | // 返回日志类对象
196 | return new Log($config, LogLevel::DEBUG);
197 | }
198 |
199 | /**
200 | * 创建日志目录.
201 | *
202 | * @param string $log_path 日志目录
203 | *
204 | * @return bool
205 | */
206 | private function createLogPath(string $log_path)
207 | {
208 | $dirs = explode("/", $log_path);
209 | $current_dir = "";
210 | foreach ($dirs as $dir) {
211 | $current_dir .= $dir;
212 | if (file_exists($current_dir) && !is_dir($current_dir)) {
213 | @unlink($current_dir);
214 | }
215 | $current_dir .= "/";
216 | if (!file_exists($current_dir)) {
217 | @mkdir($current_dir, 0755);
218 | }
219 | }
220 | return true;
221 | }
222 |
223 | /**
224 | * 获取日志文件名称.
225 | *
226 | * @return string
227 | */
228 | private function getLogFile()
229 | {
230 | // 创建日期时间对象writeFile
231 | $dt = new \DateTime();
232 | // 计算日志目录格式
233 | return sprintf('%s/%s/%s', $this->logPath, $dt->format($this->format), $this->logFile);
234 | }
235 |
236 | /**
237 | * 协程写文件
238 | *
239 | * @param string $logFile 日志路径
240 | * @param string $messageText 文本信息
241 | */
242 | private function coWrite(string $logFile, string $messageText)
243 | {
244 | \Swoole\Coroutine::create(function () use ($logFile, $messageText) {
245 | $res = Coroutine::writeFile($logFile, $messageText, FILE_APPEND);
246 | if ($res === false) {
247 | throw new \InvalidArgumentException("Unable to append to log file: {$this->logFile}");
248 | }
249 | });
250 | }
251 |
252 | /**
253 | * 同步写文件
254 | *
255 | * @param string $logFile 日志路径
256 | * @param string $messageText 文本信息
257 | */
258 | private function syncWrite(string $logFile, string $messageText)
259 | {
260 | $fp = fopen($logFile, 'a');
261 | if ($fp === false) {
262 | throw new \InvalidArgumentException("Unable to append to log file: {$this->logFile}");
263 | }
264 | flock($fp, LOCK_EX);
265 | fwrite($fp, $messageText);
266 | flock($fp, LOCK_UN);
267 | fclose($fp);
268 | }
269 | }
270 |
--------------------------------------------------------------------------------
/src/MysqlClient.php:
--------------------------------------------------------------------------------
1 |
7 | * Date: 2018/10/26
8 | * Time: 下午5:45.
9 | */
10 | abstract class MysqlClient extends Base
11 | {
12 | public $connName;
13 | public $client;
14 | public $database;
15 | public $model;
16 | public $ssl = false;
17 |
18 | public function connect(string $host, int $port, float $timeout = 0.1)
19 | {
20 | }
21 |
22 | public function onClientReceive(\Swoole\Coroutine\Client $cli, string $data)
23 | {
24 | }
25 |
26 | public function onClientClose(\Swoole\Coroutine\Client $cli)
27 | {
28 | }
29 |
30 | public function onClientError(\Swoole\Coroutine\Client $cli)
31 | {
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/MysqlPacket/AuthPacket.php:
--------------------------------------------------------------------------------
1 |
13 | * Date: 2018/10/31
14 | * Time: 上午10:32.
15 | */
16 | class AuthPacket extends MySQLPacket
17 | {
18 | private static $FILLER = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; //23位array
19 |
20 | public $clientFlags;
21 | public $maxPacketSize;
22 | public $charsetIndex;
23 | public $extra; // from FILLER(23)
24 | public $user;
25 | public $password;
26 | public $database = 0;
27 | public $pluginName = 'mysql_native_password';
28 | public $serverCapabilities;
29 |
30 | public function read(BinaryPacket $bin)
31 | {
32 | $this->packetLength = $bin->packetLength;
33 | $this->packetId = $bin->packetId;
34 | $mm = new MySQLMessage($bin->data);
35 | $mm->move(4);
36 | $this->clientFlags = $mm->readUB4();
37 | $this->maxPacketSize = $mm->readUB4();
38 | $this->charsetIndex = ($mm->read() & 0xff);
39 | $current = $mm->position();
40 | $len = (int) $mm->readLength();
41 | if ($len > 0 && $len < count(self::$FILLER)) {
42 | $this->extra = array_copy($mm->bytes(), $mm->position(), $len);
43 | }
44 | $mm->position($current + count(self::$FILLER));
45 | $this->user = $mm->readStringWithNull();
46 | $this->password = $mm->readBytesWithLength();
47 | if ((0 != ($this->clientFlags & Capabilities::CLIENT_CONNECT_WITH_DB)) && $mm->hasRemaining()) {
48 | $this->database = $mm->readStringWithNull();
49 | }
50 | $this->pluginName = $mm->readStringWithNull();
51 |
52 | return $this;
53 | }
54 |
55 | public function write()
56 | {
57 | $data = getMysqlPackSize($this ->calcPacketSize());
58 | $data[] = $this->packetId;
59 | BufferUtil::writeUB4($data, $this->clientFlags);
60 | BufferUtil::writeUB4($data, $this->maxPacketSize);
61 | $data[] = $this->charsetIndex;
62 |
63 | $data = array_merge($data, self::$FILLER);
64 |
65 | if (null == $this->user) {
66 | $data[] = 0;
67 | } else {
68 | BufferUtil::writeWithNull($data, getBytes($this->user));
69 | }
70 | if (null == $this->password) {
71 | $authResponseLength = 0;
72 | $authResponse = 0;
73 | } else {
74 | $authResponseLength = count($this->password);
75 | $authResponse = $this->password;
76 | }
77 | if ($this ->clientFlags & Capabilities::CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) {
78 | BufferUtil::writeLength($data, $authResponseLength);
79 | BufferUtil::writeWithNull($data, $authResponse, false);
80 | } else if ($this ->clientFlags & Capabilities::CLIENT_SECURE_CONNECTION) {
81 | $data[] = $authResponseLength;
82 | BufferUtil::writeWithNull($data, $authResponse, false);
83 | } else {
84 | BufferUtil::writeWithNull($data, $authResponse);
85 | }
86 |
87 | if ($this ->clientFlags & Capabilities::CLIENT_CONNECT_WITH_DB) {
88 | $database = getBytes($this->database);
89 | BufferUtil::writeWithNull($data, $database);
90 | }
91 | if ($this ->clientFlags & Capabilities::CLIENT_PLUGIN_AUTH) {
92 | BufferUtil::writeWithNull($data, getBytes($this->pluginName));
93 | }
94 | return $data;
95 | }
96 |
97 | public function calcPacketSize()
98 | {
99 | $size = 32; // 4+4+1+23;
100 | $size += (null == $this->user) ? 1 : strlen($this->user) + 1;
101 | if ($this ->clientFlags & Capabilities::CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) {
102 | $size += BufferUtil::getLength(count($this->password)) - 1;
103 | }
104 | $size += (null == $this->password) ? 1 : BufferUtil::getLength($this->password);
105 | if ($this ->clientFlags & Capabilities::CLIENT_CONNECT_WITH_DB) {
106 | $size += (null == $this->database) ? 1 : strlen($this->database) + 1;
107 | }
108 | if ($this ->clientFlags & Capabilities::CLIENT_PLUGIN_AUTH) {
109 | $size += strlen($this ->pluginName) + 1;
110 | }
111 |
112 | return $size;
113 | }
114 |
115 | protected function getPacketInfo()
116 | {
117 | return 'MySQL Authentication Packet';
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/MysqlPacket/BinaryPacket.php:
--------------------------------------------------------------------------------
1 |
7 | * Date: 2018/10/25
8 | * Time: 下午6:35.
9 | */
10 |
11 | /**
12 | * MySql包 外层结构.
13 | *
14 | * @Author Louis Livi <574747417@qq.com>
15 | */
16 | class BinaryPacket extends MySQLPacket
17 | {
18 | public static $OK = 1;
19 | public static $ERROR = 2;
20 | public static $HEADER = 3;
21 | public static $FIELD = 4;
22 | public static $FIELD_EOF = 5;
23 | public static $ROW = 6;
24 | public static $PACKET_EOF = 7;
25 | public $data;
26 |
27 | public function calcPacketSize()
28 | {
29 | return null == $this->data ? 0 : count($this->data);
30 | }
31 |
32 | protected function getPacketInfo()
33 | {
34 | return 'MySQL Binary Packet';
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/MysqlPacket/ErrorPacket.php:
--------------------------------------------------------------------------------
1 |
13 | * Date: 2018/10/29
14 | * Time: 下午4:11.
15 | */
16 | class ErrorPacket extends MySQLPacket
17 | {
18 | public static $FIELD_COUNT = 255;
19 | public $marker = '#';
20 | public $sqlState = 'HY000';
21 | public $errno = ErrorCode::ER_NO_SUCH_USER;
22 | public $message;
23 |
24 | public function read(BinaryPacket $bin)
25 | {
26 | $this->packetLength = $bin->packetLength;
27 | $this->packetId = $bin->packetId;
28 | $mm = new MySQLMessage($bin->data);
29 | $mm->move(4);
30 | $this->fieldCount = $mm->read();
31 | $this->errno = $mm->readUB2();
32 | if ($mm->hasRemaining() && chr($mm->read($mm->position()) == $this->marker)) {
33 | $mm->read();
34 | $this->sqlState = getString($mm->readBytes(5));
35 | }
36 | $this->message = getString($mm->readBytes());
37 |
38 | return $this;
39 | }
40 |
41 | public function write()
42 | {
43 | $data = [];
44 | $size = $this->calcPacketSize();
45 | $data = array_merge($data, $size);
46 | $data[] = $this->packetId;
47 | $data[] = self::$FIELD_COUNT;
48 | BufferUtil::writeUB2($data, $this->errno);
49 | $data[] = ord($this->marker);
50 | $data = array_merge($data, getBytes($this->sqlState));
51 | if (null != $this->message) {
52 | $data = array_merge($data, getBytes($this->message));
53 | }
54 |
55 | return $data;
56 | }
57 |
58 | public function calcPacketSize()
59 | {
60 | $size = 9;
61 | if (null != $this->message) {
62 | $sizeData = getMysqlPackSize($size + strlen($this->message));
63 | } else {
64 | $sizeData[] = $size;
65 | $sizeData[] = 0;
66 | $sizeData[] = 0;
67 | }
68 |
69 | return $sizeData;
70 | }
71 |
72 | protected function getPacketInfo()
73 | {
74 | return 'MySQL Error Packet';
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/MysqlPacket/HandshakePacket.php:
--------------------------------------------------------------------------------
1 | packetLength = $bin->packetLength;
32 | $this->packetId = $bin->packetId;
33 | $mm = new MySQLMessage($bin->data);
34 | $mm->length = $this->packetLength;
35 | $mm->move(4);
36 | $this->protocolVersion = $mm->read();
37 | $this->serverVersion = $mm->readStringWithNull();
38 | $this->threadId = $mm->readUB4();
39 | $this->seed = $mm->readBytesWithNull();
40 | $this->serverCapabilities = $mm->readUB2();
41 | $this->serverCharsetIndex = $mm->read();
42 | $this->serverStatus = $mm->readUB2();
43 | $this->serverCapabilities |= $mm->readUB2();
44 | $this->authDataLength = $mm->read();
45 | $mm->move(10);
46 | if ($this ->serverCapabilities & Capabilities::CLIENT_SECURE_CONNECTION) {
47 | $this->restOfScrambleBuff = $mm->readBytesWithNull();
48 | }
49 | $this->pluginName = $mm->readStringWithNull() ?: $this->pluginName;
50 | return $this;
51 | }
52 |
53 | public function write()
54 | {
55 | // default init 256,so it can avoid buff extract
56 | $buffer = [];
57 | BufferUtil::writeUB3($buffer, $this->calcPacketSize());
58 | $buffer[] = $this->packetId;
59 | $buffer[] = $this->protocolVersion;
60 | BufferUtil::writeWithNull($buffer, getBytes($this->serverVersion));
61 | BufferUtil::writeUB4($buffer, $this->threadId);
62 | BufferUtil::writeWithNull($buffer, $this->seed);
63 | BufferUtil::writeUB2($buffer, $this->serverCapabilities);
64 | $buffer[] = $this->serverCharsetIndex;
65 | BufferUtil::writeUB2($buffer, $this->serverStatus);
66 | if ($this ->serverCapabilities & Capabilities::CLIENT_PLUGIN_AUTH) {
67 | BufferUtil::writeUB2($buffer, $this->serverCapabilities);
68 | $buffer[] = max(13, count($this->seed) + count($this->restOfScrambleBuff) + 1);
69 | $buffer = array_merge($buffer, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
70 | } else {
71 | $buffer = array_merge($buffer, self::$FILLER_13);
72 | }
73 | if ($this ->serverCapabilities & Capabilities::CLIENT_SECURE_CONNECTION) {
74 | BufferUtil::writeWithNull($buffer, $this->restOfScrambleBuff);
75 | }
76 | if ($this ->serverCapabilities & Capabilities::CLIENT_PLUGIN_AUTH) {
77 | BufferUtil::writeWithNull($buffer, getBytes($this->pluginName));
78 | }
79 | return $buffer;
80 | }
81 |
82 | public function calcPacketSize()
83 | {
84 | $size = 1;
85 | $size += strlen($this->serverVersion); // n
86 | $size += 5; // 1+4
87 | $size += count($this->seed); // 8
88 | $size += 19; // 1+2+1+2+13
89 | if ($this ->serverCapabilities & Capabilities::CLIENT_SECURE_CONNECTION) {
90 | $size += count($this->restOfScrambleBuff); // 12
91 | ++$size; // 1
92 | }
93 | if ($this ->serverCapabilities & Capabilities::CLIENT_PLUGIN_AUTH) {
94 | $size += strlen($this->pluginName);
95 | ++$size; // 1
96 | }
97 | return $size;
98 | }
99 |
100 | protected function getPacketInfo()
101 | {
102 | return 'MySQL Handshake Packet';
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/MysqlPacket/MySQLMessage.php:
--------------------------------------------------------------------------------
1 |
10 | * Date: 2018/10/25
11 | * Time: 下午6:42.
12 | */
13 |
14 | /**
15 | * For netty MySql.
16 | *
17 | * @author lizhuyang
18 | */
19 | class MySQLMessage
20 | {
21 | public static $NULL_LENGTH = -1;
22 | private static $EMPTY_BYTES = 0;
23 |
24 | private $data;
25 | public $length;
26 | private $position;
27 |
28 | public function __construct(array $data)
29 | {
30 | $this->data = $data;
31 | $this->length = count($data);
32 | $this->position = 0;
33 | }
34 |
35 | public function length()
36 | {
37 | return $this->length;
38 | }
39 |
40 | public function position(int $i = 0)
41 | {
42 | if ($i) {
43 | $this->position = $i;
44 | } else {
45 | return $this->position;
46 | }
47 | }
48 |
49 | public function bytes()
50 | {
51 | return $this->data;
52 | }
53 |
54 | public function move(int $i)
55 | {
56 | $this->position += $i;
57 | }
58 |
59 | public function hasRemaining()
60 | {
61 | return $this->length > $this->position;
62 | }
63 |
64 | public function read(int $i = 0)
65 | {
66 | if ($i) {
67 | return $this->data[$i];
68 | }
69 |
70 | return $this->data[$this->position++];
71 | }
72 |
73 | public function readUB2()
74 | {
75 | $b = $this->data;
76 | $i = $b[$this->position++];
77 | $i |= ($b[$this->position++]) << 8;
78 |
79 | return $i;
80 | }
81 |
82 | public function readUB3()
83 | {
84 | $b = $this->data;
85 | $i = $b[$this->position++];
86 | $i |= ($b[$this->position++]) << 8;
87 | $i |= ($b[$this->position++]) << 16;
88 |
89 | return $i;
90 | }
91 |
92 | public function readUB4()
93 | {
94 | $b = $this->data;
95 | $l = $b[$this->position++];
96 | $l |= $b[$this->position++] << 8;
97 | $l |= $b[$this->position++] << 16;
98 | $l |= $b[$this->position++] << 24;
99 |
100 | return $l;
101 | }
102 |
103 | public function readInt()
104 | {
105 | $b = $this->data;
106 | $i = $b[$this->position++];
107 | $i |= ($b[$this->position++]) << 8;
108 | $i |= ($b[$this->position++]) << 16;
109 | $i |= ($b[$this->position++]) << 24;
110 |
111 | return $i;
112 | }
113 |
114 | public function readFloat()
115 | {
116 | return (float) ($this->readInt());
117 | }
118 |
119 | public function readLong()
120 | {
121 | $b = $this->data;
122 | $l = $b[$this->position++];
123 | $l |= $b[$this->position++] << 8;
124 | $l |= $b[$this->position++] << 16;
125 | $l |= $b[$this->position++] << 24;
126 | $l |= $b[$this->position++] << 32;
127 | $l |= $b[$this->position++] << 40;
128 | $l |= $b[$this->position++] << 48;
129 | $l |= $b[$this->position++] << 56;
130 |
131 | return $l;
132 | }
133 |
134 | public function readDouble()
135 | {
136 | return $this->readLong();
137 | }
138 |
139 | public function readLength()
140 | {
141 | $length = ($this->data[$this->position++] ?? 0) & 0xff;
142 | switch ($length) {
143 | case 251:
144 | return self::$NULL_LENGTH;
145 | case 252:
146 | return $this->readUB2();
147 | case 253:
148 | return $this->readUB3();
149 | case 254:
150 | return $this->readLong();
151 | default:
152 | return $length;
153 | }
154 | }
155 |
156 | public function readBytes(int $length = 0)
157 | {
158 | if ($length) {
159 | return array_copy($this->data, $this->position, $length);
160 | } else {
161 | if ($this->position >= $this->length) {
162 | return self::$EMPTY_BYTES;
163 | }
164 |
165 | return array_copy($this->data, $this->position, $this->length - $this->position);
166 | }
167 | }
168 |
169 | public function readBytesWithNull()
170 | {
171 | $b = $this->data;
172 | if ($this->position >= $this->length) {
173 | return self::$EMPTY_BYTES;
174 | }
175 | $offset = -1;
176 | for ($i = $this->position; $i < $this->length; ++$i) {
177 | if (0 == $b[$i]) {
178 | $offset = $i;
179 | break;
180 | }
181 | }
182 | switch ($offset) {
183 | case -1:
184 | $ab1 = array_copy($b, $this->position, $this->length - $this->position);
185 | $this->position = $this->length;
186 |
187 | return $ab1;
188 | case 0:
189 | $this->position++;
190 |
191 | return self::$EMPTY_BYTES;
192 | default:
193 | $ab2 = array_copy($b, $this->position, $offset - $this->position);
194 | $this->position = $offset + 1;
195 |
196 | return $ab2;
197 | }
198 | }
199 |
200 | public function readBytesWithLength()
201 | {
202 | $length = (int) $this->readLength();
203 | if ($length <= 0) {
204 | return [self::$EMPTY_BYTES];
205 | }
206 | $ab = array_copy($this->data, $this->position, $length);
207 | $this->position += $length;
208 |
209 | return $ab;
210 | }
211 |
212 | public function readStringWithNull(string $charset = '')
213 | {
214 | $b = $this->data;
215 | if ($this->position >= $this->length) {
216 | return null;
217 | }
218 | $offset = -1;
219 | for ($i = $this->position; $i < $this->length; ++$i) {
220 | if (0 == $b[$i]) {
221 | $offset = $i;
222 | break;
223 | }
224 | }
225 | if ($charset) {
226 | switch ($offset) {
227 | case -1:
228 | $s1 = getString(array_copy($b, $this->position, $this->length - $this->position));
229 | $this->position = $this->length;
230 |
231 | return $s1;
232 | case 0:
233 | $this->position++;
234 |
235 | return null;
236 | default:
237 | $s2 = getString(array_copy($b, $this->position, $offset - $this->position));
238 | $this->position = $offset + 1;
239 |
240 | return $s2;
241 | }
242 | } else {
243 | if (-1 == $offset) {
244 | $s = getString(array_copy($b, $this->position, $this->length - $this->position));
245 | $this->position = $this->length;
246 |
247 | return $s;
248 | }
249 | if ($offset > $this->position) {
250 | $s = getString(array_copy($b, $this->position, $offset - $this->position));
251 | $this->position = $offset + 1;
252 |
253 | return $s;
254 | } else {
255 | ++$this->position;
256 |
257 | return null;
258 | }
259 | }
260 | }
261 |
262 | public function readString()
263 | {
264 | if ($this->position >= $this->length) {
265 | return null;
266 | }
267 | $s = getString(array_copy($this->data, $this->position, $this->length - $this->position));
268 | $this->position = $this->length;
269 |
270 | return $s;
271 | }
272 | }
273 |
--------------------------------------------------------------------------------
/src/MysqlPacket/MySQLPacket.php:
--------------------------------------------------------------------------------
1 |
7 | * Date: 2018/10/25
8 | * Time: 下午6:26.
9 | */
10 |
11 | /**
12 | * MySqlPacket.
13 | *
14 | * @Author Louis Livi <574747417@qq.com>
15 | */
16 | abstract class MySQLPacket
17 | {
18 | /**
19 | * none, this is an internal thread state.
20 | */
21 | public static $COM_SLEEP = 0;
22 |
23 | /**
24 | * mysql_close.
25 | */
26 | public static $COM_QUIT = 1;
27 |
28 | /**
29 | * mysql_select_db.
30 | */
31 | public static $COM_INIT_DB = 2;
32 |
33 | /**
34 | * mysql_real_query.
35 | */
36 | public static $COM_QUERY = 3;
37 |
38 | /**
39 | * mysql_list_fields.
40 | */
41 | public static $COM_FIELD_LIST = 4;
42 |
43 | /**
44 | * mysql_create_db (deprecated).
45 | */
46 | public static $COM_CREATE_DB = 5;
47 |
48 | /**
49 | * mysql_drop_db (deprecated).
50 | */
51 | public static $COM_DROP_DB = 6;
52 |
53 | /**
54 | * mysql_refresh.
55 | */
56 | public static $COM_REFRESH = 7;
57 |
58 | /**
59 | * mysql_shutdown.
60 | */
61 | public static $COM_SHUTDOWN = 8;
62 |
63 | /**
64 | * mysql_stat.
65 | */
66 | public static $COM_STATISTICS = 9;
67 |
68 | /**
69 | * mysql_list_processes.
70 | */
71 | public static $COM_PROCESS_INFO = 10;
72 |
73 | /**
74 | * none, this is an internal thread state.
75 | */
76 | public static $COM_CONNECT = 11;
77 |
78 | /**
79 | * mysql_kill.
80 | */
81 | public static $COM_PROCESS_KILL = 12;
82 |
83 | /**
84 | * mysql_dump_debug_info.
85 | */
86 | public static $COM_DEBUG = 13;
87 |
88 | /**
89 | * mysql_ping.
90 | */
91 | public static $COM_PING = 14;
92 |
93 | /**
94 | * none, this is an internal thread state.
95 | */
96 | public static $COM_TIME = 15;
97 |
98 | /**
99 | * none, this is an internal thread state.
100 | */
101 | public static $COM_DELAYED_INSERT = 16;
102 |
103 | /**
104 | * mysql_change_user.
105 | */
106 | public static $COM_CHANGE_USER = 17;
107 |
108 | /**
109 | * used by slave server mysqlbinlog.
110 | */
111 | public static $COM_BINLOG_DUMP = 18;
112 |
113 | /**
114 | * used by slave server to get master table.
115 | */
116 | public static $COM_TABLE_DUMP = 19;
117 |
118 | /**
119 | * used by slave to log connection to master.
120 | */
121 | public static $COM_CONNECT_OUT = 20;
122 |
123 | /**
124 | * used by slave to register to master.
125 | */
126 | public static $COM_REGISTER_SLAVE = 21;
127 |
128 | /**
129 | * mysql_stmt_prepare.
130 | */
131 | public static $COM_STMT_PREPARE = 22;
132 |
133 | /**
134 | * mysql_stmt_execute.
135 | */
136 | public static $COM_STMT_EXECUTE = 23;
137 |
138 | /**
139 | * mysql_stmt_send_long_data.
140 | */
141 | public static $COM_STMT_SEND_LONG_DATA = 24;
142 |
143 | /**
144 | * mysql_stmt_close.
145 | */
146 | public static $COM_STMT_CLOSE = 25;
147 |
148 | /**
149 | * mysql_stmt_reset.
150 | */
151 | public static $COM_STMT_RESET = 26;
152 |
153 | /**
154 | * mysql_set_server_option.
155 | */
156 | public static $COM_SET_OPTION = 27;
157 |
158 | /**
159 | * mysql_stmt_fetch.
160 | */
161 | public static $COM_STMT_FETCH = 28;
162 |
163 | /**
164 | * cobar heartbeat.
165 | */
166 | public static $COM_HEARTBEAT = 64;
167 |
168 | /**
169 | * MORE RESULTS.
170 | */
171 | public static $SERVER_MORE_RESULTS_EXISTS = 8;
172 |
173 | public $packetLength;
174 | public $packetId = 1;
175 |
176 | /**
177 | * 把数据包写到buffer中,如果buffer满了就把buffer通过前端连接写出。
178 | *
179 | * @throws \Exception
180 | */
181 | public function write()
182 | {
183 | throw new \Exception();
184 | }
185 |
186 | /**
187 | * @param $buffer
188 | * @param $ctx
189 | *
190 | * @throws \Exception
191 | */
192 | public function writeBuf($buffer, $ctx)
193 | {
194 | throw new \Exception();
195 | }
196 |
197 | /**
198 | * 计算数据包大小,不包含包头长度。
199 | */
200 | abstract public function calcPacketSize();
201 |
202 | /**
203 | * 取得数据包信息.
204 | */
205 | abstract protected function getPacketInfo();
206 |
207 | protected function toString()
208 | {
209 | return $this->getPacketInfo() . '{length=' . $this->packetLength . ',id='
210 | . $this->packetId . '}';
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/src/MysqlPacket/MySqlPacketDecoder.php:
--------------------------------------------------------------------------------
1 |
12 | * Date: 2018/10/27
13 | * Time: 上午10:37.
14 | */
15 | class MySqlPacketDecoder
16 | {
17 | private $packetHeaderSize = 4;
18 | private $maxPacketSize = 16777216;
19 |
20 | /**
21 | * MySql外层结构解包.
22 | *
23 | * @param string $data
24 | *
25 | * @return \SMProxy\MysqlPacket\BinaryPacket
26 | * @throws \SMProxy\SMProxyException
27 | */
28 | public function decode(string $data)
29 | {
30 | $data = getBytes($data);
31 | // 4 bytes:3 length + 1 packetId
32 | if (count($data) < $this->packetHeaderSize) {
33 | throw new SMProxyException('Packet is empty');
34 | }
35 | $packetLength = ByteUtil::readUB3($data);
36 | // // 过载保护
37 | if ($packetLength > $this->maxPacketSize) {
38 | throw new SMProxyException('Packet size over the limit ' . $this->maxPacketSize);
39 | }
40 | $packetId = $data[3];
41 | // if (in.readableBytes() < packetLength) {
42 | // // 半包回溯
43 | // in.resetReaderIndex();
44 | // return;
45 | // }
46 | $packet = new BinaryPacket();
47 | $packet->packetLength = $packetLength;
48 | $packet->packetId = $packetId;
49 | // data will not be accessed any more,so we can use this array safely
50 | $packet->data = $data;
51 | if (null == $packet->data || 0 == count($packet->data)) {
52 | throw new SMProxyException('get data errorMessage,packetLength=' . $packet->packetLength);
53 | }
54 |
55 | return $packet;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/MysqlPacket/OkPacket.php:
--------------------------------------------------------------------------------
1 |
9 | * Date: 2018/10/27
10 | * Time: 上午9:36.
11 | */
12 |
13 | /**
14 | * MySql OkPacket.
15 | *
16 | * @Author lizhuyang
17 | */
18 | class OkPacket extends MySQLPacket
19 | {
20 | public static $FIELD_COUNT = 0x00;
21 | public static $OK = [7, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0];
22 | public static $AUTH_OK = [7, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0];
23 | public static $FAST_AUTH_OK = [7, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0];
24 | public static $SWITCH_AUTH_OK = [7, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0];
25 | public static $FULL_AUTH_OK = [7, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0];
26 |
27 | public $fieldCount = 0x00;
28 | public $affectedRows;
29 | public $insertId;
30 | public $serverStatus;
31 | public $warningCount;
32 | public $message;
33 |
34 | public function read(BinaryPacket $bin)
35 | {
36 | $this->packetLength = $bin->packetLength;
37 | $this->packetId = $bin->packetId;
38 | $mm = new MySQLMessage($bin->data);
39 | $this->fieldCount = $mm->read();
40 | $this->affectedRows = $mm->readLength();
41 | $this->insertId = $mm->readLength();
42 | $this->serverStatus = $mm->readUB2();
43 | $this->warningCount = $mm->readUB2();
44 | if ($mm->hasRemaining()) {
45 | $this->message = $mm->readBytesWithLength();
46 | }
47 | }
48 |
49 | public function calcPacketSize()
50 | {
51 | $i = 1;
52 | $i += BufferUtil::getLength($this->affectedRows);
53 | $i += BufferUtil::getLength($this->insertId);
54 | $i += 4;
55 | if (null != $this->message) {
56 | $i += BufferUtil::getLength($this->message);
57 | }
58 |
59 | return $i;
60 | }
61 |
62 | protected function getPacketInfo()
63 | {
64 | return 'MySQL OK Packet';
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/MysqlPacket/SMProxyPacket.php:
--------------------------------------------------------------------------------
1 |
7 | * Date: 2020/1/11
8 | * Time: 上午11:07
9 | */
10 | class SMProxyPacket
11 | {
12 | public static $SMPROXY = 83;
13 | }
14 |
--------------------------------------------------------------------------------
/src/MysqlPacket/Util/BufferUtil.php:
--------------------------------------------------------------------------------
1 |
9 | * Date: 2018/10/25
10 | * Time: 下午7:22.
11 | */
12 |
13 | /**
14 | * @Author lizhuyang
15 | */
16 | class BufferUtil
17 | {
18 | public static function writeUB2(array &$buffer, int $i)
19 | {
20 | $buffer[] = $i & 0xff;
21 | $buffer[] = shr16($i & 0xff << 8, 8);
22 | }
23 |
24 | public static function writeUB3(array &$buffer, int $i)
25 | {
26 | $buffer[] = $i & 0xff;
27 | $buffer[] = shr16($i & 0xff << 8, 8);
28 | $buffer[] = shr16($i & 0xff << 16, 16);
29 | }
30 |
31 | public static function writeInt(array &$buffer, int $i)
32 | {
33 | $buffer[] = $i & 0xff;
34 | $buffer[] = shr16($i & 0xff << 8, 8);
35 | $buffer[] = shr16($i & 0xff << 16, 16);
36 | $buffer[] = shr16($i & 0xff << 24, 24);
37 | }
38 |
39 | public static function writeFloat(array &$buffer, int $f)
40 | {
41 | self::writeInt($buffer, (int) ($f));
42 | }
43 |
44 | public static function writeUB4(array &$buffer, int $l)
45 | {
46 | $buffer[] = $l & 0xff;
47 | $buffer[] = shr16($l & 0xff << 8, 8);
48 | $buffer[] = shr16($l & 0xff << 16, 16);
49 | $buffer[] = shr16($l & 0xff << 24, 24);
50 | }
51 |
52 | public static function writeLong(array &$buffer, int $l)
53 | {
54 | $buffer[] = $l & 0xff;
55 | $buffer[] = shr16($l & 0xff << 8, 8);
56 | $buffer[] = shr16($l & 0xff << 16, 16);
57 | $buffer[] = shr16($l & 0xff << 24, 24);
58 | $buffer[] = shr16($l & 0xff << 32, 32);
59 | $buffer[] = shr16($l & 0xff << 40, 40);
60 | $buffer[] = shr16($l & 0xff << 48, 48);
61 | $buffer[] = shr16($l & 0xff << 56, 56);
62 | }
63 |
64 | public static function writeDouble(array &$buffer, int $d)
65 | {
66 | self::writeLong($buffer, (float) ($d));
67 | }
68 |
69 | public static function writeLength(array &$buffer, int $l)
70 | {
71 | if ($l < 251) {
72 | $buffer[] = $l;
73 | } elseif ($l < 0x10000) {
74 | $buffer[] = 252;
75 | self::writeUB2($buffer, (int) $l);
76 | } elseif ($l < 0x1000000) {
77 | $buffer[] = 253;
78 | self::writeUB3($buffer, (int) $l);
79 | } else {
80 | $buffer[] = 254;
81 |
82 | self::writeLong($buffer, $l);
83 | }
84 | }
85 |
86 | public static function writeWithNull(array &$buffer, $src, $null = true)
87 | {
88 | $src = is_array($src) ? $src : [$src];
89 | $buffer = array_merge($buffer, $src);
90 | if ($null) {
91 | $buffer[] = 0;
92 | }
93 | }
94 |
95 | public static function writeWithLength(array &$buffer, $src, int $nullValue = 0)
96 | {
97 | if (null == $src) {
98 | $buffer[] = $nullValue;
99 | } else {
100 | $length = count($src);
101 | if ($length < 251) {
102 | $buffer[] = $length;
103 | } elseif ($length < 0x10000) {
104 | $buffer[] = 252;
105 | self::writeUB2($buffer, $length);
106 | } elseif ($length < 0x1000000) {
107 | $buffer[] = 253;
108 | self::writeUB3($buffer, $length);
109 | } else {
110 | $buffer[] = 254;
111 | self::writeLong($buffer, $length);
112 | }
113 | $buffer = array_merge($buffer, $src);
114 | }
115 | }
116 |
117 | public static function getLength($length)
118 | {
119 | if (is_array($length)) {
120 | $length = count($length);
121 | if ($length < 251) {
122 | return 1 + $length;
123 | } elseif ($length < 0x10000) {
124 | return 3 + $length;
125 | } elseif ($length < 0x1000000) {
126 | return 4 + $length;
127 | } else {
128 | return 9 + $length;
129 | }
130 | } else {
131 | if ($length < 251) {
132 | return 1;
133 | } elseif ($length < 0x10000) {
134 | return 3;
135 | } elseif ($length < 0x1000000) {
136 | return 4;
137 | } else {
138 | return 9;
139 | }
140 | }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/MysqlPacket/Util/ByteUtil.php:
--------------------------------------------------------------------------------
1 |
9 | * Date: 2018/10/27
10 | * Time: 上午10:44.
11 | */
12 | class ByteUtil
13 | {
14 | public static function readUB2(array $data)
15 | {
16 | $i = ($data[0]);
17 | $i |= ($data[1] << 8);
18 |
19 | return $i;
20 | }
21 |
22 | public static function readUB3(array $data)
23 | {
24 | $i = ($data[0]);
25 | $i |= ($data[1] << 8);
26 | $i |= ($data[2] << 16);
27 |
28 | return $i;
29 | }
30 |
31 | public static function readUB4(array $data)
32 | {
33 | $i = ($data[0]);
34 | $i |= ($data[1] << 8);
35 | $i |= ($data[2] << 16);
36 | $i |= ($data[3] << 24);
37 |
38 | return $i;
39 | }
40 |
41 | public static function readLong(array $data)
42 | {
43 | $l = ($data[0]);
44 | $l |= ($data[1]) << 8;
45 | $l |= ($data[2]) << 16;
46 | $l |= ($data[3]) << 24;
47 | $l |= ($data[4]) << 32;
48 | $l |= ($data[5]) << 40;
49 | $l |= ($data[6]) << 48;
50 | $l |= ($data[7]) << 56;
51 |
52 | return $l;
53 | }
54 |
55 | /**
56 | * this is for the String.
57 | *
58 | * @param array $data
59 | *
60 | * @return int|mixed
61 | */
62 | public static function readLength(array $data)
63 | {
64 | $length = $data[0];
65 | switch ($length) {
66 | case 251:
67 | return MySQLMessage::$NULL_LENGTH;
68 | case 252:
69 | return self::readUB2($data);
70 | case 253:
71 | return self::readUB3($data);
72 | case 254:
73 | return self::readLong($data);
74 | default:
75 | return $length;
76 | }
77 | }
78 |
79 | public static function decodeLength($src)
80 | {
81 | if (is_array($src)) {
82 | $length = count($src);
83 | if ($length < 251) {
84 | return 1 + $length;
85 | } elseif ($length < 0x10000) {
86 | return 3 + $length;
87 | } elseif ($length < 0x1000000) {
88 | return 4 + $length;
89 | } else {
90 | return 9 + $length;
91 | }
92 | } else {
93 | if ($src < 251) {
94 | return 1;
95 | } elseif ($src < 0x10000) {
96 | return 3;
97 | } elseif ($src < 0x1000000) {
98 | return 4;
99 | } else {
100 | return 9;
101 | }
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/MysqlPacket/Util/Capabilities.php:
--------------------------------------------------------------------------------
1 |
7 | * Date: 2018/10/31
8 | * Time: 上午10:39.
9 | */
10 | interface Capabilities
11 | {
12 | /**
13 | * server capabilities
14 | *
15 | *
16 | * server: 11110111 11111111
17 | * client_cmd: 11 10100110 10000101
18 | * client_jdbc:10 10100010 10001111.
19 | *
20 | *
21 | */
22 | // new more secure passwords
23 | const CLIENT_LONG_PASSWORD = 1;
24 |
25 | // Found instead of affected rows
26 | // 返回找到(匹配)的行数,而不是改变了的行数。
27 | const CLIENT_FOUND_ROWS = 2;
28 |
29 | // Get all column flags
30 | const CLIENT_LONG_FLAG = 4;
31 |
32 | // One can specify db on connect
33 | const CLIENT_CONNECT_WITH_DB = 8;
34 |
35 | // Don't allow database.table.column
36 | // 不允许“数据库名.表名.列名”这样的语法。这是对于ODBC的设置。
37 | // 当使用这样的语法时解析器会产生一个错误,这对于一些ODBC的程序限制bug来说是有用的。
38 | const CLIENT_NO_SCHEMA = 16;
39 |
40 | // Can use compression protocol
41 | // 使用压缩协议
42 | const CLIENT_COMPRESS = 32;
43 |
44 | // Odbc client
45 | const CLIENT_ODBC = 64;
46 |
47 | // Can use LOAD DATA LOCAL
48 | const CLIENT_LOCAL_FILES = 128;
49 |
50 | // Ignore spaces before '('
51 | // 允许在函数名后使用空格。所有函数名可以预留字。
52 | const CLIENT_IGNORE_SPACE = 256;
53 |
54 | // New 4.1 protocol This is an interactive client
55 | const CLIENT_PROTOCOL_41 = 512;
56 |
57 | // This is an interactive client
58 | // 允许使用关闭连接之前的不活动交互超时的描述,而不是等待超时秒数。
59 | // 客户端的会话等待超时变量变为交互超时变量。
60 | const CLIENT_INTERACTIVE = 1024;
61 |
62 | // Switch to SSL after handshake
63 | // 使用SSL。这个设置不应该被应用程序设置,他应该是在客户端库内部是设置的。
64 | // 可以在调用mysql_real_connect()之前调用mysql_ssl_set()来代替设置。
65 | const CLIENT_SSL = 2048;
66 |
67 | // IGNORE sigpipes
68 | // 阻止客户端库安装一个SIGPIPE信号处理器。
69 | // 这个可以用于当应用程序已经安装该处理器的时候避免与其发生冲突。
70 | const CLIENT_IGNORE_SIGPIPE = 4096;
71 |
72 | // Client knows about transactions
73 | const CLIENT_TRANSACTIONS = 8192;
74 |
75 | // Old flag for 4.1 protocol
76 | const CLIENT_RESERVED = 16384;
77 |
78 | // New 4.1 authentication
79 | const CLIENT_SECURE_CONNECTION = 32768;
80 |
81 | // Enable/disable multi-stmt support
82 | // 通知服务器客户端可以发送多条语句(由分号分隔)。如果该标志为没有被设置,多条语句执行。
83 | const CLIENT_MULTI_STATEMENTS = 65536;
84 |
85 | // Enable/disable multi-results
86 | // 通知服务器客户端可以处理由多语句或者存储过程执行生成的多结果集。
87 | // 当打开CLIENT_MULTI_STATEMENTS时,这个标志自动的被打开。
88 | const CLIENT_MULTI_RESULTS = 131072;
89 |
90 | const CLIENT_PLUGIN_AUTH = 524288;
91 |
92 | const CLIENT_CONNECT_ATTRS = 1048576;
93 |
94 | const CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA = 2097152;
95 |
96 | const CLIENT_CAPABILITIES = (
97 | self::CLIENT_LONG_PASSWORD | self::CLIENT_FOUND_ROWS | self::CLIENT_ODBC | self::CLIENT_LONG_FLAG |
98 | self::CLIENT_IGNORE_SPACE | self::CLIENT_PROTOCOL_41 | self::CLIENT_TRANSACTIONS |
99 | self::CLIENT_INTERACTIVE | self::CLIENT_SECURE_CONNECTION | self::CLIENT_MULTI_RESULTS |
100 | self::CLIENT_PLUGIN_AUTH | self::CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA
101 | );
102 | # Not done yet
103 | const CLIENT_HANDLE_EXPIRED_PASSWORDS = 4194304;
104 |
105 | const CLIENT_SESSION_TRACK = 8388608;
106 |
107 | const CLIENT_DEPRECATE_EOF = 16777216;
108 | }
109 |
--------------------------------------------------------------------------------
/src/MysqlPacket/Util/CharsetUtil.php:
--------------------------------------------------------------------------------
1 |
7 | * Date: 2018/10/31
8 | * Time: 下午4:13.
9 | */
10 | class CharsetUtil
11 | {
12 | private static $CHARSET_TO_INDEX = [];
13 | const INDEX_TO_CHARSET = [
14 | 1 => ['big5', 'big5_chinese_ci', true],
15 | 2 => ['latin2', 'latin2_czech_cs', false],
16 | 3 => ['dec8', 'dec8_swedish_ci', true],
17 | 4 => ['cp850', 'cp850_general_ci', true],
18 | 5 => ['latin1', 'latin1_german1_ci', false],
19 | 6 => ['hp8', 'hp8_english_ci', true],
20 | 7 => ['koi8r', 'koi8r_general_ci', true],
21 | 8 => ['latin1', 'latin1_swedish_ci', true],
22 | 9 => ['latin2', 'latin2_general_ci', true],
23 | 10 => ['swe7', 'swe7_swedish_ci', true],
24 | 11 => ['ascii', 'ascii_general_ci', true],
25 | 12 => ['ujis', 'ujis_japanese_ci', true],
26 | 13 => ['sjis', 'sjis_japanese_ci', true],
27 | 14 => ['cp1251', 'cp1251_bulgarian_ci', false],
28 | 15 => ['latin1', 'latin1_danish_ci', false],
29 | 16 => ['hebrew', 'hebrew_general_ci', true],
30 | 18 => ['tis620', 'tis620_thai_ci', true],
31 | 19 => ['euckr', 'euckr_korean_ci', true],
32 | 20 => ['latin7', 'latin7_estonian_cs', false],
33 | 21 => ['latin2', 'latin2_hungarian_ci', false],
34 | 22 => ['koi8u', 'koi8u_general_ci', true],
35 | 23 => ['cp1251', 'cp1251_ukrainian_ci', false],
36 | 24 => ['gb2312', 'gb2312_chinese_ci', true],
37 | 25 => ['greek', 'greek_general_ci', true],
38 | 26 => ['cp1250', 'cp1250_general_ci', true],
39 | 27 => ['latin2', 'latin2_croatian_ci', false],
40 | 28 => ['gbk', 'gbk_chinese_ci', true],
41 | 29 => ['cp1257', 'cp1257_lithuanian_ci', false],
42 | 30 => ['latin5', 'latin5_turkish_ci', true],
43 | 31 => ['latin1', 'latin1_german2_ci', false],
44 | 32 => ['armscii8', 'armscii8_general_ci', true],
45 | 33 => ['utf8', 'utf8_general_ci', true],
46 | 34 => ['cp1250', 'cp1250_czech_cs', false],
47 | 35 => ['ucs2', 'ucs2_general_ci', true],
48 | 36 => ['cp866', 'cp866_general_ci', true],
49 | 37 => ['keybcs2', 'keybcs2_general_ci', true],
50 | 38 => ['macce', 'macce_general_ci', true],
51 | 39 => ['macroman', 'macroman_general_ci', true],
52 | 40 => ['cp852', 'cp852_general_ci', true],
53 | 41 => ['latin7', 'latin7_general_ci', true],
54 | 42 => ['latin7', 'latin7_general_cs', false],
55 | 43 => ['macce', 'macce_bin', false],
56 | 44 => ['cp1250', 'cp1250_croatian_ci', false],
57 | 45 => ['utf8mb4', 'utf8mb4_general_ci', true],
58 | 46 => ['utf8mb4', 'utf8mb4_bin', false],
59 | 47 => ['latin1', 'latin1_bin', false],
60 | 48 => ['latin1', 'latin1_general_ci', false],
61 | 49 => ['latin1', 'latin1_general_cs', false],
62 | 50 => ['cp1251', 'cp1251_bin', false],
63 | 51 => ['cp1251', 'cp1251_general_ci', true],
64 | 52 => ['cp1251', 'cp1251_general_cs', false],
65 | 53 => ['macroman', 'macroman_bin', false],
66 | 54 => ['utf16', 'utf16_general_ci', true],
67 | 55 => ['utf16', 'utf16_bin', false],
68 | 57 => ['cp1256', 'cp1256_general_ci', true],
69 | 58 => ['cp1257', 'cp1257_bin', false],
70 | 59 => ['cp1257', 'cp1257_general_ci', true],
71 | 60 => ['utf32', 'utf32_general_ci', true],
72 | 61 => ['utf32', 'utf32_bin', false],
73 | 63 => ['binary', 'binary', true],
74 | 64 => ['armscii8', 'armscii8_bin', false],
75 | 65 => ['ascii', 'ascii_bin', false],
76 | 66 => ['cp1250', 'cp1250_bin', false],
77 | 67 => ['cp1256', 'cp1256_bin', false],
78 | 68 => ['cp866', 'cp866_bin', false],
79 | 69 => ['dec8', 'dec8_bin', false],
80 | 70 => ['greek', 'greek_bin', false],
81 | 71 => ['hebrew', 'hebrew_bin', false],
82 | 72 => ['hp8', 'hp8_bin', false],
83 | 73 => ['keybcs2', 'keybcs2_bin', false],
84 | 74 => ['koi8r', 'koi8r_bin', false],
85 | 75 => ['koi8u', 'koi8u_bin', false],
86 | 77 => ['latin2', 'latin2_bin', false],
87 | 78 => ['latin5', 'latin5_bin', false],
88 | 79 => ['latin7', 'latin7_bin', false],
89 | 80 => ['cp850', 'cp850_bin', false],
90 | 81 => ['cp852', 'cp852_bin', false],
91 | 82 => ['swe7', 'swe7_bin', false],
92 | 83 => ['utf8', 'utf8_bin', false],
93 | 84 => ['big5', 'big5_bin', false],
94 | 85 => ['euckr', 'euckr_bin', false],
95 | 86 => ['gb2312', 'gb2312_bin', false],
96 | 87 => ['gbk', 'gbk_bin', false],
97 | 88 => ['sjis', 'sjis_bin', false],
98 | 89 => ['tis620', 'tis620_bin', false],
99 | 90 => ['ucs2', 'ucs2_bin', false],
100 | 91 => ['ujis', 'ujis_bin', false],
101 | 92 => ['geostd8', 'geostd8_general_ci', true],
102 | 93 => ['geostd8', 'geostd8_bin', false],
103 | 94 => ['latin1', 'latin1_spanish_ci', false],
104 | 95 => ['cp932', 'cp932_japanese_ci', true],
105 | 96 => ['cp932', 'cp932_bin', false],
106 | 97 => ['eucjpms', 'eucjpms_japanese_ci', true],
107 | 98 => ['eucjpms', 'eucjpms_bin', false],
108 | 99 => ['cp1250', 'cp1250_polish_ci', false],
109 | 101 => ['utf16', 'utf16_unicode_ci', false],
110 | 102 => ['utf16', 'utf16_icelandic_ci', false],
111 | 103 => ['utf16', 'utf16_latvian_ci', false],
112 | 104 => ['utf16', 'utf16_romanian_ci', false],
113 | 105 => ['utf16', 'utf16_slovenian_ci', false],
114 | 106 => ['utf16', 'utf16_polish_ci', false],
115 | 107 => ['utf16', 'utf16_estonian_ci', false],
116 | 108 => ['utf16', 'utf16_spanish_ci', false],
117 | 109 => ['utf16', 'utf16_swedish_ci', false],
118 | 110 => ['utf16', 'utf16_turkish_ci', false],
119 | 111 => ['utf16', 'utf16_czech_ci', false],
120 | 112 => ['utf16', 'utf16_danish_ci', false],
121 | 113 => ['utf16', 'utf16_lithuanian_ci', false],
122 | 114 => ['utf16', 'utf16_slovak_ci', false],
123 | 115 => ['utf16', 'utf16_spanish2_ci', false],
124 | 116 => ['utf16', 'utf16_roman_ci', false],
125 | 117 => ['utf16', 'utf16_persian_ci', false],
126 | 118 => ['utf16', 'utf16_esperanto_ci', false],
127 | 119 => ['utf16', 'utf16_hungarian_ci', false],
128 | 120 => ['utf16', 'utf16_sinhala_ci', false],
129 | 128 => ['ucs2', 'ucs2_unicode_ci', false],
130 | 129 => ['ucs2', 'ucs2_icelandic_ci', false],
131 | 130 => ['ucs2', 'ucs2_latvian_ci', false],
132 | 131 => ['ucs2', 'ucs2_romanian_ci', false],
133 | 132 => ['ucs2', 'ucs2_slovenian_ci', false],
134 | 133 => ['ucs2', 'ucs2_polish_ci', false],
135 | 134 => ['ucs2', 'ucs2_estonian_ci', false],
136 | 135 => ['ucs2', 'ucs2_spanish_ci', false],
137 | 136 => ['ucs2', 'ucs2_swedish_ci', false],
138 | 137 => ['ucs2', 'ucs2_turkish_ci', false],
139 | 138 => ['ucs2', 'ucs2_czech_ci', false],
140 | 139 => ['ucs2', 'ucs2_danish_ci', false],
141 | 140 => ['ucs2', 'ucs2_lithuanian_ci', false],
142 | 141 => ['ucs2', 'ucs2_slovak_ci', false],
143 | 142 => ['ucs2', 'ucs2_spanish2_ci', false],
144 | 143 => ['ucs2', 'ucs2_roman_ci', false],
145 | 144 => ['ucs2', 'ucs2_persian_ci', false],
146 | 145 => ['ucs2', 'ucs2_esperanto_ci', false],
147 | 146 => ['ucs2', 'ucs2_hungarian_ci', false],
148 | 147 => ['ucs2', 'ucs2_sinhala_ci', false],
149 | 159 => ['ucs2', 'ucs2_general_mysql500_ci', false],
150 | 160 => ['utf32', 'utf32_unicode_ci', false],
151 | 161 => ['utf32', 'utf32_icelandic_ci', false],
152 | 162 => ['utf32', 'utf32_latvian_ci', false],
153 | 163 => ['utf32', 'utf32_romanian_ci', false],
154 | 164 => ['utf32', 'utf32_slovenian_ci', false],
155 | 165 => ['utf32', 'utf32_polish_ci', false],
156 | 166 => ['utf32', 'utf32_estonian_ci', false],
157 | 167 => ['utf32', 'utf32_spanish_ci', false],
158 | 168 => ['utf32', 'utf32_swedish_ci', false],
159 | 169 => ['utf32', 'utf32_turkish_ci', false],
160 | 170 => ['utf32', 'utf32_czech_ci', false],
161 | 171 => ['utf32', 'utf32_danish_ci', false],
162 | 172 => ['utf32', 'utf32_lithuanian_ci', false],
163 | 173 => ['utf32', 'utf32_slovak_ci', false],
164 | 174 => ['utf32', 'utf32_spanish2_ci', false],
165 | 175 => ['utf32', 'utf32_roman_ci', false],
166 | 176 => ['utf32', 'utf32_persian_ci', false],
167 | 177 => ['utf32', 'utf32_esperanto_ci', false],
168 | 178 => ['utf32', 'utf32_hungarian_ci', false],
169 | 179 => ['utf32', 'utf32_sinhala_ci', false],
170 | 192 => ['utf8', 'utf8_unicode_ci', false],
171 | 193 => ['utf8', 'utf8_icelandic_ci', false],
172 | 194 => ['utf8', 'utf8_latvian_ci', false],
173 | 195 => ['utf8', 'utf8_romanian_ci', false],
174 | 196 => ['utf8', 'utf8_slovenian_ci', false],
175 | 197 => ['utf8', 'utf8_polish_ci', false],
176 | 198 => ['utf8', 'utf8_estonian_ci', false],
177 | 199 => ['utf8', 'utf8_spanish_ci', false],
178 | 200 => ['utf8', 'utf8_swedish_ci', false],
179 | 201 => ['utf8', 'utf8_turkish_ci', false],
180 | 202 => ['utf8', 'utf8_czech_ci', false],
181 | 203 => ['utf8', 'utf8_danish_ci', false],
182 | 204 => ['utf8', 'utf8_lithuanian_ci', false],
183 | 205 => ['utf8', 'utf8_slovak_ci', false],
184 | 206 => ['utf8', 'utf8_spanish2_ci', false],
185 | 207 => ['utf8', 'utf8_roman_ci', false],
186 | 208 => ['utf8', 'utf8_persian_ci', false],
187 | 209 => ['utf8', 'utf8_esperanto_ci', false],
188 | 210 => ['utf8', 'utf8_hungarian_ci', false],
189 | 211 => ['utf8', 'utf8_sinhala_ci', false],
190 | 223 => ['utf8', 'utf8_general_mysql500_ci', false],
191 | 224 => ['utf8mb4', 'utf8mb4_unicode_ci', false],
192 | 225 => ['utf8mb4', 'utf8mb4_icelandic_ci', false],
193 | 226 => ['utf8mb4', 'utf8mb4_latvian_ci', false],
194 | 227 => ['utf8mb4', 'utf8mb4_romanian_ci', false],
195 | 228 => ['utf8mb4', 'utf8mb4_slovenian_ci', false],
196 | 229 => ['utf8mb4', 'utf8mb4_polish_ci', false],
197 | 230 => ['utf8mb4', 'utf8mb4_estonian_ci', false],
198 | 231 => ['utf8mb4', 'utf8mb4_spanish_ci', false],
199 | 232 => ['utf8mb4', 'utf8mb4_swedish_ci', false],
200 | 233 => ['utf8mb4', 'utf8mb4_turkish_ci', false],
201 | 234 => ['utf8mb4', 'utf8mb4_czech_ci', false],
202 | 235 => ['utf8mb4', 'utf8mb4_danish_ci', false],
203 | 236 => ['utf8mb4', 'utf8mb4_lithuanian_ci', false],
204 | 237 => ['utf8mb4', 'utf8mb4_slovak_ci', false],
205 | 238 => ['utf8mb4', 'utf8mb4_spanish2_ci', false],
206 | 239 => ['utf8mb4', 'utf8mb4_roman_ci', false],
207 | 240 => ['utf8mb4', 'utf8mb4_persian_ci', false],
208 | 241 => ['utf8mb4', 'utf8mb4_esperanto_ci', false],
209 | 242 => ['utf8mb4', 'utf8mb4_hungarian_ci', false],
210 | 243 => ['utf8mb4', 'utf8mb4_sinhala_ci', false],
211 | 244 => ['utf8mb4', 'utf8mb4_german2_ci', false],
212 | 245 => ['utf8mb4', 'utf8mb4_croatian_ci', false],
213 | 246 => ['utf8mb4', 'utf8mb4_unicode_520_ci', false],
214 | 247 => ['utf8mb4', 'utf8mb4_vietnamese_ci', false],
215 | ];
216 |
217 | public static function init()
218 | {
219 | // charset --> index
220 | foreach (self::INDEX_TO_CHARSET as $index => $charset) {
221 | if (!isset(self::$CHARSET_TO_INDEX[$charset[0]]) || $charset[2]) {
222 | self::$CHARSET_TO_INDEX[$charset[0]] = $index;
223 | }
224 | }
225 |
226 | self::$CHARSET_TO_INDEX['iso-8859-1'] = 14;
227 | self::$CHARSET_TO_INDEX['utf-8'] = 33;
228 | }
229 |
230 | public static function getCharset(int $index)
231 | {
232 | self::init();
233 | return self::INDEX_TO_CHARSET[$index][0];
234 | }
235 |
236 | public static function getIndex(string $charset)
237 | {
238 | self::init();
239 | if (null == $charset || 0 == strlen($charset)) {
240 | return 0;
241 | } else {
242 | $i = self::$CHARSET_TO_INDEX[strtolower($charset)];
243 |
244 | return (null == $i) ? 0 : $i;
245 | }
246 | }
247 |
248 | public static function charsetToEncoding($charset)
249 | {
250 | if ($charset == 'utf8mb4') {
251 | return 'utf-8';
252 | } elseif ($charset == 'utf8') {
253 | return 'utf-8';
254 | }
255 | return $charset;
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/src/MysqlPacket/Util/RandomUtil.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 2018/11/9
5 | * Time: 上午9:41.
6 | */
7 |
8 | namespace SMProxy\MysqlPacket\Util;
9 |
10 | use function SMProxy\Helper\shr16;
11 |
12 | /**
13 | * 随机数类.
14 | *
15 | * Class RandomUtil
16 | */
17 | class RandomUtil
18 | {
19 | private static $bytes = [
20 | '1',
21 | '2',
22 | '3',
23 | '4',
24 | '5',
25 | '6',
26 | '7',
27 | '8',
28 | '9',
29 | '0',
30 | 'q',
31 | 'w',
32 | 'e',
33 | 'r',
34 | 't',
35 | 'y', 'u', 'i', 'o', 'p', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'z', 'x', 'c', 'v', 'b', 'n', 'm',
36 | 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'Z', 'X',
37 | 'C', 'V', 'B', 'N', 'M',
38 | ];
39 | private static $multiplier = 0x5DEECE66D;
40 | private static $addend = 0xB;
41 | private static $mask = (1 << 48) - 1;
42 | private static $integerMask = (1 << 33) - 1;
43 | private static $seedUniquifier = 8682522807148012;
44 |
45 | private static $seed;
46 |
47 | public function __construct()
48 | {
49 | $s = self::$seedUniquifier + system('date +%s%N');
50 | $s = ($s ^ self::$multiplier) & self::$mask;
51 | $this->seed = $s;
52 | }
53 |
54 | public static function randomBytes(int $size)
55 | {
56 | $bb = self::$bytes;
57 | $ab = new \SplFixedArray($size);
58 | for ($i = 0; $i < $size; ++$i) {
59 | $ab[$i] = array_rand($bb);
60 | }
61 |
62 | return $ab->toArray();
63 | }
64 |
65 | private static function randomByte(array $b)
66 | {
67 | $ran = (int) (shr16((self::next() & self::$integerMask) & 0xff << 16, 16));
68 |
69 | return $b[$ran % count($b)];
70 | }
71 |
72 | private static function next()
73 | {
74 | $oldSeed = self::$seed;
75 | $nextSeed = 0;
76 | do {
77 | $nextSeed = ($oldSeed * self::$multiplier + self::$addend) & self::$mask;
78 | } while ($oldSeed == $nextSeed);
79 | self::$seed = $nextSeed;
80 |
81 | return $nextSeed;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/MysqlPacket/Util/SecurityUtil.php:
--------------------------------------------------------------------------------
1 |
10 | * Date: 2018/10/31
11 | * Time: 下午2:18.
12 | */
13 | class SecurityUtil
14 | {
15 | public static function scramble411(string $pass, array $seed)
16 | {
17 | $pass1 = getBytes(sha1($pass, true));
18 | $pass2 = getBytes(sha1(getString($pass1), true));
19 | $pass3 = getBytes(sha1(getString($seed) . getString($pass2), true));
20 | for ($i = 0, $count = count($pass3); $i < $count; ++$i) {
21 | $pass3[$i] = ($pass3[$i] ^ $pass1[$i]);
22 | }
23 |
24 | return $pass3;
25 | }
26 |
27 | public static function scrambleSha256(string $pass, array $seed)
28 | {
29 | $pass1 = getBytes(hash('sha256', $pass, true));
30 | $pass2 = getBytes(hash('sha256', getString($pass1), true));
31 | $pass3 = getBytes(hash('sha256', getString($pass2) . getString($seed), true));
32 | for ($i = 0, $count = count($pass3); $i < $count; ++$i) {
33 | $pass1[$i] ^= $pass3[$i];
34 | }
35 | return $pass1;
36 | }
37 |
38 | private static function xorPassword($password, $salt)
39 | {
40 | $password_bytes = getBytes($password);
41 | $password_bytes[] = 0;
42 | $salt_len = count($salt);
43 | for ($i = 0, $count = count($password_bytes); $i < $count; ++$i) {
44 | $password_bytes[$i] ^= $salt[$i % $salt_len];
45 | }
46 | return getString($password_bytes);
47 | }
48 |
49 |
50 | public static function sha2RsaEncrypt($password, $salt, $publicKey)
51 | {
52 | /*Encrypt password with salt and public_key.
53 |
54 | Used for sha256_password and caching_sha2_password.
55 | */
56 | $message = self::xorPassword($password, $salt);
57 | openssl_public_encrypt($message, $encrypted, $publicKey, OPENSSL_PKCS1_OAEP_PADDING);
58 | return $encrypted;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/MysqlPacket/Util/Versions.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 2018/11/9
5 | * Time: 上午10:11.
6 | */
7 |
8 | namespace SMProxy\MysqlPacket\Util;
9 |
10 | interface Versions
11 | {
12 | /** 协议版本 */
13 | const PROTOCOL_VERSION = 10;
14 |
15 | /** 服务器版本 */
16 | const SERVER_VERSION = '5.6.0-SMProxy';
17 | }
18 |
--------------------------------------------------------------------------------
/src/MysqlPool/MySQLException.php:
--------------------------------------------------------------------------------
1 |
7 | * Date: 2018/11/6
8 | * Time: 上午10:53.
9 | */
10 | class MySQLException extends \Exception
11 | {
12 | public function errorMessage()
13 | {
14 | return sprintf('%s (%s:%s)', trim($this->getMessage()), $this->getFile(), $this->getLine());
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/MysqlProxy.php:
--------------------------------------------------------------------------------
1 |
25 | * Date: 2018/10/26
26 | * Time: 下午5:51.
27 | */
28 | class MysqlProxy extends MysqlClient
29 | {
30 | private $isDuplex;
31 | public $server;
32 | public $serverFd;
33 | public $charset;
34 | public $account;
35 | public $auth = false;
36 | public $chan;
37 | public $serverPublicKey;
38 | public $salt;
39 | public $connected = false;
40 | public $timeout = 0.1;
41 | public $mysqlClient;
42 | public $mysqlServer;
43 |
44 | /**
45 | * MysqlClient constructor.
46 | *
47 | */
48 | public function __construct(\swoole_server $server, int $fd, \Swoole\Coroutine\Channel $chan)
49 | {
50 | $this->server = $server;
51 | $this->serverFd = $fd;
52 | $this->chan = $chan;
53 | $this->client = new Client(CONFIG['server']['swoole_client_sock_setting']['sock_type'] ?? SWOOLE_SOCK_TCP);
54 | $this->client->set(CONFIG['server']['swoole_client_setting'] ?? []);
55 | $this->client->set(packageLengthSetting());
56 | $this->isDuplex = version_compare(SWOOLE_VERSION, '4.2.13', '>=');
57 | if (!$this->isDuplex) {
58 | $this->mysqlClient = new Channel(1);
59 | }
60 | }
61 |
62 | /**
63 | * connect.
64 | *
65 | * @param string $host
66 | * @param int $port
67 | * @param float $timeout
68 | * @param int $tryStep
69 | *
70 | * @return bool|Client
71 | */
72 | public function connect(string $host, int $port, float $timeout = 0.1, int $tryStep = 0)
73 | {
74 | $this->timeout = $timeout;
75 | if (!$this->client->connect($host, $port, $timeout)) {
76 | if ($tryStep < 3) {
77 | $this->client->close();
78 | return $this->connect($host, $port, $timeout, ++$tryStep);
79 | } else {
80 | $this->onClientError($this->client);
81 | return false;
82 | }
83 | } else {
84 | if (!$this->isDuplex) {
85 | $this->mysqlClient->push($this->client);
86 | }
87 | self::go(function () {
88 | $remain = '';
89 | while (true) {
90 | $data = $this->recv($remain);
91 | if ($data === '' || $data === false) {
92 | break;
93 | }
94 | }
95 | });
96 | return $this->client;
97 | }
98 | }
99 |
100 | /**
101 | * mysql 客户端消息转发.
102 | *
103 | * @param $cli
104 | * @param $data
105 | */
106 | public function onClientReceive(\Swoole\Coroutine\Client $cli, string $data)
107 | {
108 | self::go(function () use ($cli, $data) {
109 | $fd = $this->serverFd;
110 | $binaryPacket = new BinaryPacket();
111 | $binaryPacket->data = getBytes($data);
112 | $binaryPacket->packetLength = $binaryPacket->calcPacketSize();
113 | if (isset($binaryPacket->data[4])) {
114 | $send = true;
115 | //ERROR Packet
116 | if ($binaryPacket->data[4] == ErrorPacket::$FIELD_COUNT) {
117 | $errorPacket = new ErrorPacket();
118 | $errorPacket->read($binaryPacket);
119 | //$errorPacket->errno = ErrorCode::ER_SYNTAX_ERROR;
120 | $data = getString($errorPacket->write());
121 | } elseif (!$this->connected) {
122 | //OK Packet
123 | if ($binaryPacket->data[4] == OkPacket::$FIELD_COUNT) {
124 | $send = false;
125 | $this->connected = true;
126 | $this->chan->push($this);
127 | # 快速认证
128 | } elseif ($binaryPacket->data[4] == 0x01) {
129 | # 请求公钥
130 | if ($binaryPacket->packetLength == 6) {
131 | if ($binaryPacket->data[$binaryPacket->packetLength - 1] == 4) {
132 | $data = getString(array_merge(getMysqlPackSize(1), [3, 2]));
133 | $this->send($data);
134 | }
135 | } else {
136 | $this->serverPublicKey = substr($data, 5, strlen($data) - 2);
137 | $encryptData = SecurityUtil::sha2RsaEncrypt($this->account['password'], $this->salt, $this->serverPublicKey);
138 | $data = getString(array_merge(getMysqlPackSize(strlen($encryptData)), [5])) . $encryptData;
139 | $this->send($data);
140 | }
141 | $send = false;
142 | //EOF Packet
143 | } elseif ($binaryPacket->data[4] == 0xfe) {
144 | $mm = new MySQLMessage($binaryPacket->data);
145 | $mm->move(5);
146 | $pluginName = $mm->readStringWithNull();
147 | $this->salt = $mm->readBytesWithNull();
148 | $password = $this->processAuth($pluginName ?: 'mysql_native_password');
149 | $this->send(getString(array_merge(getMysqlPackSize(count($password)), [3], $password)));
150 | $send = false;
151 | //未授权
152 | } elseif (!$this->auth) {
153 | $handshakePacket = (new HandshakePacket())->read($binaryPacket);
154 | $this->mysqlServer = $handshakePacket;
155 | $this->salt = array_merge($handshakePacket->seed, $handshakePacket->restOfScrambleBuff);
156 | $password = $this->processAuth($handshakePacket->pluginName);
157 | $clientFlag = Capabilities::CLIENT_CAPABILITIES;
158 | $authPacket = new AuthPacket();
159 | $authPacket->pluginName = $handshakePacket->pluginName;
160 | $authPacket->packetId = 1;
161 | if (isset($this->database) && $this->database) {
162 | $authPacket->database = $this->database;
163 | } else {
164 | $authPacket->database = 0;
165 | }
166 | if ($authPacket->database) {
167 | $clientFlag |= Capabilities::CLIENT_CONNECT_WITH_DB;
168 | }
169 | if (version_compare($handshakePacket->serverVersion, '5.0', '>=')) {
170 | $clientFlag |= Capabilities::CLIENT_MULTI_RESULTS;
171 | }
172 | $authPacket->clientFlags = $clientFlag;
173 | $authPacket->serverCapabilities = $handshakePacket->serverCapabilities;
174 | $authPacket->maxPacketSize =
175 | CONFIG['server']['swoole_client_setting']['package_max_length'] ?? 16777215;
176 | $authPacket->charsetIndex = CharsetUtil::getIndex($this->charset ?? 'utf8mb4');
177 | $authPacket->user = $this->account['user'];
178 | $authPacket->password = $password;
179 | $this->auth = true;
180 | $this->send(getString($authPacket->write()));
181 | $send = false;
182 | }
183 | }
184 | if ($send && $this->server->exist($fd)) {
185 | $this->server->send($fd, $data);
186 | }
187 | }
188 | });
189 | }
190 |
191 | /**
192 | * 认证
193 | *
194 | * @param string $pluginName
195 | *
196 | * @return array
197 | * @throws MySQLException
198 | */
199 | public function processAuth(string $pluginName)
200 | {
201 | switch ($pluginName) {
202 | case 'mysql_native_password':
203 | $password = SecurityUtil::scramble411($this->account['password'], $this->salt);
204 | break;
205 | case 'caching_sha2_password':
206 | $password = SecurityUtil::scrambleSha256($this->account['password'], $this->salt);
207 | break;
208 | case 'sha256_password':
209 | throw new MySQLException('Sha256_password plugin is not supported yet');
210 | break;
211 | case 'mysql_old_password':
212 | throw new MySQLException('mysql_old_password plugin is not supported yet');
213 | break;
214 | case 'mysql_clear_password':
215 | $password = array_merge(getBytes($this->account['password']), [0]);
216 | break;
217 | default:
218 | $password = SecurityUtil::scramble411($this->account['password'], $this->salt);
219 | break;
220 | }
221 | return $password;
222 | }
223 |
224 | /**
225 | * send.
226 | *
227 | * @param mixed ...$data
228 | *
229 | * @return bool
230 | */
231 | public function send(...$data)
232 | {
233 | if ($this->isDuplex) {
234 | if ($this->client->isConnected()) {
235 | return $this->client->send(...$data);
236 | } else {
237 | return false;
238 | }
239 | } else {
240 | $client = self::coPop($this->mysqlClient);
241 | if ($client === false) {
242 | //连接关闭或超时
243 | return false;
244 | }
245 | if ($client->isConnected()) {
246 | $result = $client->send(...$data);
247 | $this->mysqlClient->push($client);
248 | return $result;
249 | }
250 | return false;
251 | }
252 | }
253 |
254 | /**
255 | * recv.
256 | *
257 | * @return mixed
258 | */
259 | public function recv(&$remain)
260 | {
261 | if ($this->isDuplex) {
262 | $client = $this->client;
263 | $data = $client->recv(-1);
264 | } else {
265 | $client = self::coPop($this->mysqlClient, $this->timeout);
266 | if ($client === false) {
267 | //连接关闭或超时
268 | return false;
269 | }
270 | if ($client->isConnected()) {
271 | $data = $client->recv($this->timeout / 500);
272 | } else {
273 | $data = '';
274 | }
275 | $this->mysqlClient->push($client);
276 | if ($data === false && $client->errCode == 110) {
277 | //如果连接超时则不处理
278 | $data = true;
279 | }
280 | if ($data === '' || $data === false) {
281 | $this->mysqlClient->close();
282 | }
283 | }
284 | if ($data === '' || $data === false) {
285 | $this->onClientClose($client);
286 | } elseif (is_string($data)) {
287 | $this->onClientReceive($client, $data);
288 | }
289 | return $data;
290 | }
291 |
292 | /**
293 | * close.
294 | *
295 | * @param Client $cli
296 | */
297 | public function onClientClose(\Swoole\Coroutine\Client $cli)
298 | {
299 | MySQLPool::destruct($cli, $this->connName);
300 | }
301 |
302 | public function onClientError(\Swoole\Coroutine\Client $cli)
303 | {
304 | }
305 | }
306 |
--------------------------------------------------------------------------------
/src/Parser/Util/CharTypes.php:
--------------------------------------------------------------------------------
1 |
7 | * Date: 2018/11/3
8 | * Time: 上午9:48.
9 | */
10 | class CharTypes
11 | {
12 | public static function isIdentifierChar(int $c2)
13 | {
14 | $identifierFlags = new \SplFixedArray(256);
15 | for ($c = 0; $c < count($identifierFlags); ++$c) {
16 | if ($c >= 'A' && $c <= 'Z') {
17 | $identifierFlags[$c] = true;
18 | } elseif ($c >= 'a' && $c <= 'z') {
19 | $identifierFlags[$c] = true;
20 | } elseif ($c >= '0' && $c <= '9') {
21 | $identifierFlags[$c] = true;
22 | }
23 | }
24 | // identifierFlags['`'] = true;
25 | $identifierFlags['_'] = true;
26 | $identifierFlags['$'] = true;
27 |
28 | return $c2 > count($identifierFlags) || $identifierFlags[$c2];
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Parser/Util/ParseUtil.php:
--------------------------------------------------------------------------------
1 |
7 | * Date: 2018/11/3
8 | * Time: 上午9:30.
9 | */
10 | final class ParseUtil
11 | {
12 | public static function isEOF($c)
13 | {
14 | return ' ' == $c || "\t" == $c || "\n" == $c || "\r" == $c || ';' == $c;
15 | }
16 |
17 | public static function getSQLId(string $stmt)
18 | {
19 | $offset = strpos($stmt, '=');
20 | if (-1 != $offset && strlen($stmt) > ++$offset) {
21 | $id = trim(substr($stmt, $offset));
22 |
23 | return $id;
24 | }
25 |
26 | return 0;
27 | }
28 |
29 | /**
30 | * 'abc'
.
31 | *
32 | * @param string $stmt
33 | * @param int offset stmt.charAt(offset) == first '
34 | *
35 | * @return string
36 | */
37 | private static function parsestring(string $stmt, int $offset)
38 | {
39 | $sb = '';
40 | $stmtLen = strlen($stmt);
41 | for (++$offset; $offset < $stmtLen; ++$offset) {
42 | $c = $stmt[$offset];
43 | if ('\\' == $c) {
44 | switch ($c = $stmt[++$offset]) {
45 | case '0':
46 | $sb .= "\0";
47 | break;
48 | case 'b':
49 | $sb .= "\b";
50 | break;
51 | case 'n':
52 | $sb .= "\n";
53 | break;
54 | case 'r':
55 | $sb .= "\r";
56 | break;
57 | case 't':
58 | $sb .= "\t";
59 | break;
60 | case 'Z':
61 | $sb .= chr(26);
62 | break;
63 | default:
64 | $sb .= $c;
65 | }
66 | } elseif ('\'' == $c) {
67 | if ($offset + 1 < strlen($stmt) && '\'' == $stmt[$offset + 1]) {
68 | ++$offset;
69 | $sb .= '\'';
70 | } else {
71 | break;
72 | }
73 | } else {
74 | $sb .= $c;
75 | }
76 | }
77 |
78 | return $sb;
79 | }
80 |
81 | /**
82 | * "abc"
.
83 | *
84 | * @param string $stmt
85 | * @param int offset stmt.charAt(offset) == first "
86 | *
87 | * @return string
88 | */
89 | private static function parsestring2(string $stmt, int $offset)
90 | {
91 | $sb = '';
92 | $stmtLen = strlen($stmt);
93 | for (++$offset; $offset < $stmtLen; ++$offset) {
94 | $c = $stmt = [$offset];
95 | if ('\\' == $c) {
96 | switch ($c = $stmt[++$offset]) {
97 | case '0':
98 | $sb .= "\0";
99 | break;
100 | case 'b':
101 | $sb .= "\b";
102 | break;
103 | case 'n':
104 | $sb .= "\n";
105 | break;
106 | case 'r':
107 | $sb .= "\r";
108 | break;
109 | case 't':
110 | $sb .= "\t";
111 | break;
112 | case 'Z':
113 | $sb .= chr(26);
114 | break;
115 | default:
116 | $sb .= $c;
117 | }
118 | } elseif ('"' == $c) {
119 | if ($offset + 1 < strlen($stmt) && '"' == $stmt[$offset + 1]) {
120 | ++$offset;
121 | $sb .= '"';
122 | } else {
123 | break;
124 | }
125 | } else {
126 | $sb .= $c;
127 | }
128 | }
129 |
130 | return $sb;
131 | }
132 |
133 | /**
134 | * AS `abc`
.
135 | *
136 | * @param string $stmt
137 | * @param int $offset
138 | *
139 | * @return string
140 | */
141 | private static function parseIdentifierEscape(string $stmt, int $offset)
142 | {
143 | $sb = '';
144 | for (++$offset,$stemLen = strlen($stmt); $offset < $stemLen; ++$offset) {
145 | $c = $stmt[$offset];
146 | if ('`' == $c) {
147 | if ($offset + 1 < strlen($stmt) && '`' == $stmt[$offset + 1]) {
148 | ++$offset;
149 | $sb .= '`';
150 | } else {
151 | break;
152 | }
153 | } else {
154 | $sb .= $c;
155 | }
156 | }
157 |
158 | return $sb;
159 | }
160 |
161 | /**
162 | * @param string $stmt
163 | * @param int $aliasIndex
164 | * @return bool|null|string
165 | */
166 | public static function parseAlias(string $stmt, int $aliasIndex)
167 | {
168 | if ($aliasIndex < 0 || $aliasIndex >= strlen($stmt)) {
169 | return null;
170 | }
171 | switch ($stmt[$aliasIndex]) {
172 | case '\'':
173 | return self::parsestring($stmt, $aliasIndex);
174 | case '"':
175 | return self::parsestring2($stmt, $aliasIndex);
176 | case '`':
177 | return self::parseIdentifierEscape($stmt, $aliasIndex);
178 | default:
179 | $offset = $aliasIndex;
180 | $stmtLen = strlen($stmt);
181 | while ($offset < $stmtLen && CharTypes::isIdentifierChar($stmt[$offset])) {
182 | $offset++;
183 | }
184 |
185 | return substr($stmt, $aliasIndex, $offset);
186 | }
187 | }
188 |
189 | public static function comment(string $stmt, int $offset)
190 | {
191 | $len = strlen($stmt);
192 | $n = $offset;
193 | switch ($stmt[$n]) {
194 | case '/':
195 | if ($len > ++$n && '*' == $stmt[$n++] && $len > $n + 1 && '!' != $stmt[$n]) {
196 | for ($i = $n; $i < $len; ++$i) {
197 | if ('*' == $stmt[$i]) {
198 | $m = $i + 1;
199 | if ($len > $m && '/' == $stmt[$m]) {
200 | return $m;
201 | }
202 | }
203 | }
204 | }
205 | break;
206 | case '#':
207 | for ($i = $n + 1; $i < $len; ++$i) {
208 | if ("\n" == $stmt[$i]) {
209 | return $i;
210 | }
211 | }
212 | break;
213 | }
214 |
215 | return $offset;
216 | }
217 |
218 | public static function currentCharIsSep(string $stmt, int $offset)
219 | {
220 | if (strlen($stmt) > $offset) {
221 | switch ($stmt[$offset]) {
222 | case ' ':
223 | case "\t":
224 | case "\r":
225 | case "\n":
226 | return true;
227 | default:
228 | return false;
229 | }
230 | }
231 |
232 | return true;
233 | }
234 |
235 | /*****
236 | * 检查下一个字符是否为分隔符,并把偏移量加1
237 | * @param string $stmt
238 | * @param int $offset
239 | * @return bool
240 | */
241 | public static function nextCharIsSep(string $stmt, int $offset)
242 | {
243 | return self::currentCharIsSep($stmt, ++$offset);
244 | }
245 |
246 | /*****
247 | * 检查下一个字符串是否为期望的字符串,并把偏移量移到从offset开始计算,expectValue之后的位置
248 | *
249 | * @param string $stmt 被解析的sql
250 | * @param int $offset 被解析的sql的当前位置
251 | * @param string $nextExpectedstring 在stmt中准备查找的字符串
252 | * @param bool $checkSepChar 当找到expectValue值时,是否检查其后面字符为分隔符号
253 | *
254 | * @return int 如果包含指定的字符串,则移动相应的偏移量,否则返回值=offset
255 | */
256 | public static function nextstringIsExpectedWithIgnoreSepChar(
257 | string $stmt,
258 | int $offset,
259 | string $nextExpectedstring,
260 | bool $checkSepChar
261 | ) {
262 | if (null == $nextExpectedstring || strlen($nextExpectedstring) < 1) {
263 | return $offset;
264 | }
265 | $i = $offset;
266 | $index = 0;
267 | for ($stmtLen = strlen($stmt); $i < $stmtLen && $index < strlen($nextExpectedstring); ++$i) {
268 | if (0 == $index) {
269 | $isSep = self::currentCharIsSep($stmt, $i);
270 | if ($isSep) {
271 | continue;
272 | }
273 | }
274 | $actualChar = $stmt[$i];
275 | $expectedChar = $nextExpectedstring[$index++];
276 | if ($actualChar != $expectedChar) {
277 | return $offset;
278 | }
279 | }
280 | if ($index == strlen($nextExpectedstring)) {
281 | $ok = true;
282 | if ($checkSepChar) {
283 | $ok = self::nextCharIsSep($stmt, $i);
284 | }
285 | if ($ok) {
286 | return $i;
287 | }
288 | }
289 |
290 | return $offset;
291 | }
292 |
293 | const JSON = 'json';
294 | const EQ = '=';
295 |
296 | //private static final string WHERE = "where";
297 | //private static final string SET = "set";
298 |
299 | /**********
300 | * 检查下一个字符串是否json= *
301 | *
302 | * @param string $stmt 被解析的sql
303 | * @param int $offset
304 | * @return int 如果包含指定的字符串,则移动相应的偏移量,否则返回值=offset
305 | */
306 | public static function nextstringIsJsonEq(string $stmt, int $offset)
307 | {
308 | $i = $offset;
309 |
310 | // / drds 之后的符号
311 | if (!self::currentCharIsSep($stmt, ++$i)) {
312 | return $offset;
313 | }
314 |
315 | // json 串
316 | $k = self::nextstringIsExpectedWithIgnoreSepChar($stmt, $i, self::JSON, false);
317 | if ($k <= $i) {
318 | return $offset;
319 | }
320 | $i = $k;
321 |
322 | // 等于符号
323 | $k = self::nextstringIsExpectedWithIgnoreSepChar($stmt, $i, self::EQ, false);
324 | if ($k <= $i) {
325 | return $offset;
326 | }
327 |
328 | return $i;
329 | }
330 |
331 | public static function move(string $stmt, int $offset, int $length)
332 | {
333 | $stmtLen = strlen($stmt);
334 | for ($i = $offset; $i < $stmtLen; ++$i) {
335 | switch ($stmt[$i]) {
336 | case ' ':
337 | case "\t":
338 | case "\r":
339 | case "\n":
340 | continue 2;
341 | case '/':
342 | case '#':
343 | $i = self::comment($stmt, $i);
344 | continue 2;
345 | default:
346 | return $i + $length;
347 | }
348 | }
349 |
350 | return $i;
351 | }
352 |
353 | public static function compare(string $s, int $offset, $keyword)
354 | {
355 | if (strlen($s) >= $offset + count($keyword)) {
356 | for ($i = 0; $i < count($keyword); ++$i, ++$offset) {
357 | if (strtoupper($s[$offset]) != $keyword[$i]) {
358 | return false;
359 | }
360 | }
361 |
362 | return true;
363 | }
364 |
365 | return false;
366 | }
367 | }
368 |
--------------------------------------------------------------------------------
/src/Route/RouteService.php:
--------------------------------------------------------------------------------
1 |
7 | * Date: 2018/12/10
8 | * Time: 下午4:11
9 | */
10 | class RouteService
11 | {
12 | const HINT_SPLIT = '=';
13 | const SMPROXY_HINT_TYPE = "_smproxyHintType";
14 |
15 | public static function route(string $stmt)
16 | {
17 | $hintLength = self::isHintSql($stmt);
18 | if ($hintLength != -1) {
19 | $endPos = strpos($stmt, "*/");
20 | if ($endPos > 0) {
21 | $hint = trim(substr($stmt, $hintLength, $endPos - $hintLength));
22 | $firstSplitPos = strpos($hint, self::HINT_SPLIT);
23 | if ($firstSplitPos > 0) {
24 | $hintArr = self::parseHint($hint);
25 | return $hintArr;
26 | }
27 | }
28 | }
29 | return [];
30 | }
31 |
32 | public static function isHintSql(string $sql)
33 | {
34 | $j = 0;
35 | $len = strlen($sql);
36 | if ($sql[$j++] == '/' && $sql[$j++] == '*') {
37 | $c = $sql[$j];
38 | // 过滤掉 空格 和 * 两种字符, 支持: "/** !smproxy: */" 和 "/** #smproxy: */" 形式的注解
39 | while ($j < $len && $c != '!' && $c != '#' && ($c == ' ' || $c == '*')) {
40 | $c = $sql[++$j];
41 | }
42 | if ($sql[$j] == 's') {
43 | $j--;
44 | }
45 | if ($j + 6 >= $len) {
46 | return -1;
47 | }
48 | if ($sql[++$j] == 's' && $sql[++$j] == 'm' && $sql[++$j] == 'p'
49 | && $sql[++$j] == 'r' && $sql[++$j] == 'o' && $sql[++$j] == 'x' && $sql[++$j] == 'y' && ($sql[++$j] == ':' || $sql[++$j] == '#')) {
50 | return $j + 1; // true,同时返回注解部分的长度
51 | }
52 | }
53 | return -1; // false
54 | }
55 |
56 | private static function parseHint(string $sql)
57 | {
58 | $arr = [];
59 | $y = 0;
60 | $begin = 0;
61 | for ($i = 0; $i < strlen($sql); $i++) {
62 | $cur = $sql[$i];
63 | if ($cur == ',' && $y % 2 == 0) {
64 | $substring = substr($sql, $begin, $i);
65 |
66 | self::parseKeyValue($arr, $substring);
67 | $begin = $i + 1;
68 | } elseif ($cur == '\'') {
69 | $y++;
70 | }
71 | if ($i == strlen($sql) - 1) {
72 | self::parseKeyValue($arr, substr($sql, $begin));
73 | }
74 | }
75 | return $arr;
76 | }
77 |
78 | private static function parseKeyValue(array &$arr, string $substring)
79 | {
80 | $indexOf = strpos($substring, '=');
81 | if ($indexOf != -1) {
82 | $key = strtolower(trim(substr($substring, 0, $indexOf)));
83 | $value = substr($substring, $indexOf + 1, strlen($substring));
84 | if (\SMProxy\Helper\endsWith($value, "'") && \SMProxy\Helper\startsWith($value, "'")) {
85 | $value = substr($value, 1, strlen($value) - 1);
86 | }
87 | if ($value == '') {
88 | $arr[self::SMPROXY_HINT_TYPE] = $key;
89 | }
90 | $arr[$key] = trim($value);
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/SMProxyException.php:
--------------------------------------------------------------------------------
1 |
7 | * Date: 2018/10/26
8 | * Time: 下午5:56.
9 | */
10 | class SMProxyException extends \Exception
11 | {
12 | public function errorMessage()
13 | {
14 | return sprintf('%s (%s:%s)', trim($this->getMessage()), $this->getFile(), $this->getLine());
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/conf/database.json:
--------------------------------------------------------------------------------
1 | {
2 | "database": {
3 | "account": {
4 | "root": {
5 | "user": "root",
6 | "password": "123456"
7 | }
8 | },
9 | "serverInfo": {
10 | "server1": {
11 | "write": {
12 | "host": ["127.0.0.1"],
13 | "port": 3306,
14 | "timeout": 0.5,
15 | "account": "root"
16 | },
17 | "read": {
18 | "host": ["127.0.0.1"],
19 | "port": 3306,
20 | "timeout": 0.5,
21 | "account": "root"
22 | }
23 | }
24 | },
25 | "databases": {
26 | "mysql": {
27 | "serverInfo": "server1",
28 | "startConns": "swoole_cpu_num()*10",
29 | "maxSpareConns": "swoole_cpu_num()*10",
30 | "maxSpareExp": 3600,
31 | "maxConns": "swoole_cpu_num()*20",
32 | "charset": "utf-8"
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/conf/server.json:
--------------------------------------------------------------------------------
1 | {
2 | "server": {
3 | "user": "root",
4 | "password": "123456",
5 | "charset": "utf8mb4",
6 | "host": "0.0.0.0",
7 | "port": "3366",
8 | "mode": "SWOOLE_PROCESS",
9 | "sock_type": "SWOOLE_SOCK_TCP",
10 | "logs": {
11 | "open":true,
12 | "config": {
13 | "system": {
14 | "log_path": "ROOT/logs",
15 | "log_file": "system.log",
16 | "format": "Y/m/d"
17 | },
18 | "mysql": {
19 | "log_path": "ROOT/logs",
20 | "log_file": "mysql.log",
21 | "format": "Y/m/d"
22 | }
23 | }
24 | },
25 | "swoole": {
26 | "worker_num": "swoole_cpu_num()",
27 | "max_coro_num": 6000,
28 | "open_tcp_nodelay": true,
29 | "daemonize": true,
30 | "heartbeat_check_interval": 60,
31 | "heartbeat_idle_time": 600,
32 | "reload_async": true,
33 | "log_file": "ROOT/logs/swoole.log",
34 | "pid_file": "ROOT/logs/pid/server.pid"
35 | },
36 | "swoole_client_setting": {
37 | "package_max_length": 16777216
38 | },
39 | "swoole_client_sock_setting": {
40 | "sock_type": "SWOOLE_SOCK_TCP"
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/tests/run:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | EXEC="`dirname $0`/../bin/SMProxy"
4 | CONFIG="`dirname $0`/../tests/conf"
5 |
6 | $EXEC start -c $CONFIG
--------------------------------------------------------------------------------
/tests/test.php:
--------------------------------------------------------------------------------
1 | query($sql);
20 | fwrite(STDOUT, 'Executed query:' . $sql . PHP_EOL);
21 | if ($result->rowCount()) {
22 | fwrite(STDOUT, 'Result: ' . json_encode($result->fetch()) . PHP_EOL);
23 | } else {
24 | fwrite(STDERR, "Result empty!" . PHP_EOL);
25 | }
26 | unset($conn);
27 | } catch (\Exception $exception) {
28 | fwrite(STDERR, $exception ->getMessage());
29 | }
30 | ?>
31 |
--------------------------------------------------------------------------------