Passed
Push — master ( da3f2a...c03036 )
by Shahrad
01:59
created

WscMain::read()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 27
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 17
nc 4
nop 1
dl 0
loc 27
c 1
b 0
f 0
cc 4
rs 9.7
1
<?php
2
//
3
//namespace EasyHttp\Utils;
4
//
5
//use EasyHttp\Contracts\CommonsContract;
6
//use EasyHttp\Contracts\WscCommonsContract;
7
//use EasyHttp\Exceptions\BadOpcodeException;
8
//use EasyHttp\Exceptions\ConnectionException;
9
//use EasyHttp\Traits\WSClientTrait;
10
//use EasyHttp\WebSocketConfig;
11
//use InvalidArgumentException;
12
//
13
///**
14
// * WscMain class
15
// *
16
// * @link    https://github.com/shahradelahi/easy-http
17
// * @author  Arthur Kushman (https://github.com/arthurkushman)
18
// * @license https://github.com/shahradelahi/easy-http/blob/master/LICENSE (MIT License)
19
// */
20
//class WscMain implements WscCommonsContract
21
//{
22
//
23
//	use WSClientTrait;
24
//
25
//	/**
26
//	 * @var resource|bool
27
//	 */
28
//	private $socket;
29
//
30
//	/**
31
//	 * @var bool
32
//	 */
33
//	private bool $isConnected = false;
34
//
35
//	/**
36
//	 * @var bool
37
//	 */
38
//	private bool $isClosing = false;
39
//
40
//	/**
41
//	 * @var string
42
//	 */
43
//	private string $lastOpcode;
44
//
45
//	/**
46
//	 * @var float|int
47
//	 */
48
//	private float|int $closeStatus;
49
//
50
//	/**
51
//	 * @var string|null
52
//	 */
53
//	private ?string $hugePayload;
54
//
55
//	/**
56
//	 * @var array|int[]
57
//	 */
58
//	private static array $opcodes = [
59
//		CommonsContract::EVENT_TYPE_CONTINUATION => 0,
60
//		CommonsContract::EVENT_TYPE_TEXT => 1,
61
//		CommonsContract::EVENT_TYPE_BINARY => 2,
62
//		CommonsContract::EVENT_TYPE_CLOSE => 8,
63
//		CommonsContract::EVENT_TYPE_PING => 9,
64
//		CommonsContract::EVENT_TYPE_PONG => 10,
65
//	];
66
//
67
//	/**
68
//	 * @var WebSocketConfig
69
//	 */
70
//	protected WebSocketConfig $config;
71
//
72
//	/**
73
//	 * @var string
74
//	 */
75
//	protected string $socketUrl;
76
//
77
//	/**
78
//	 * @param WebSocketConfig $config
79
//	 * @throws \Exception
80
//	 */
81
//	protected function connect(WebSocketConfig $config): void
82
//	{
83
//		$this->config = $config;
84
//		$urlParts = parse_url($this->socketUrl);
85
//
86
//		$this->config->setScheme($urlParts['scheme']);
87
//		$this->config->setHost($urlParts['host']);
88
//		$this->config->setUser($urlParts);
89
//		$this->config->setPassword($urlParts);
90
//		$this->config->setPort($urlParts);
91
//
92
//		$pathWithQuery = $this->getPathWithQuery($urlParts);
93
//		$hostUri = $this->getHostUri($this->config);
94
//
95
//		$context = $this->getStreamContext();
96
//		if ($this->config->hasProxy()) {
97
//			$this->socket = $this->proxy();
98
//		} else {
99
//			$this->socket = @stream_socket_client(
100
//				$hostUri . ':' . $this->config->getPort(),
101
//				$errno,
102
//				$errstr,
103
//				$this->config->getTimeout(),
104
//				STREAM_CLIENT_CONNECT,
105
//				$context
106
//			);
107
//		}
108
//
109
//		if ($this->socket === false) {
110
//			throw new ConnectionException(
111
//				"Could not open socket to \"{$this->config->getHost()}:{$this->config->getPort()}\": $errstr ($errno).",
112
//				CommonsContract::CLIENT_COULD_NOT_OPEN_SOCKET
113
//			);
114
//		}
115
//
116
//		stream_set_timeout($this->socket, $this->config->getTimeout());
117
//
118
//		$key = $this->generateKey();
119
//		$headers = [
120
//			'Host' => $this->config->getHost() . ':' . $this->config->getPort(),
121
//			'User-Agent' => 'websocket-client-php',
122
//			'Connection' => 'Upgrade',
123
//			'Upgrade' => 'WebSocket',
124
//			'Sec-WebSocket-Key' => $key,
125
//			'Sec-Websocket-Version' => '13',
126
//		];
127
//
128
//		if ($this->config->getUser() || $this->config->getPassword()) {
129
//			$headers['authorization'] = 'Basic ' . base64_encode($this->config->getUser() . ':' . $this->config->getPassword()) . "\r\n";
130
//		}
131
//
132
//		if (!empty($this->config->getHeaders())) {
133
//			$headers = array_merge($headers, $this->config->getHeaders());
134
//		}
135
//
136
//		$header = $this->getHeaders($pathWithQuery, $headers);
137
//
138
//		$this->write($header);
139
//
140
//		$this->validateResponse($this->config, $pathWithQuery, $key);
141
//		$this->isConnected = true;
142
//	}
143
//
144
//	/**
145
//	 * Init a proxy connection
146
//	 *
147
//	 * @return resource|false
148
//	 * @throws InvalidArgumentException
149
//	 * @throws ConnectionException
150
//	 */
151
//	private function proxy()
152
//	{
153
//		$sock = @stream_socket_client(
154
//			WscCommonsContract::TCP_SCHEME . $this->config->getProxyIp() . ':' . $this->config->getProxyPort(),
155
//			$errno,
156
//			$errstr,
157
//			$this->config->getTimeout(),
158
//			STREAM_CLIENT_CONNECT,
159
//			$this->getStreamContext()
160
//		);
161
//		$write = "CONNECT {$this->config->getProxyIp()}:{$this->config->getProxyPort()} HTTP/1.1\r\n";
162
//		$auth = $this->config->getProxyAuth();
163
//		if ($auth !== NULL) {
164
//			$write .= "Proxy-Authorization: Basic {$auth}\r\n";
165
//		}
166
//		$write .= "\r\n";
167
//		fwrite($sock, $write);
168
//		$resp = fread($sock, 1024);
169
//
170
//		if (preg_match(self::PROXY_MATCH_RESP, $resp) === 1) {
171
//			return $sock;
172
//		}
173
//
174
//		throw new ConnectionException('Failed to connect to the host via proxy');
175
//	}
176
//
177
//	/**
178
//	 * @return mixed
179
//	 * @throws \InvalidArgumentException
180
//	 */
181
//	private function getStreamContext(): mixed
182
//	{
183
//		if ($this->config->getContext() !== null) {
184
//			// Suppress the error since we'll catch it below
185
//			if (@get_resource_type($this->config->getContext()) === 'stream-context') {
186
//				return $this->config->getContext();
187
//			}
188
//
189
//			throw new \InvalidArgumentException(
190
//				'Stream context is invalid',
191
//				CommonsContract::CLIENT_INVALID_STREAM_CONTEXT
192
//			);
193
//		}
194
//
195
//		return stream_context_create($this->config->getContextOptions());
196
//	}
197
//
198
//	/**
199
//	 * @param mixed $urlParts
200
//	 * @return string
201
//	 */
202
//	private function getPathWithQuery(mixed $urlParts): string
203
//	{
204
//		$path = isset($urlParts['path']) ? $urlParts['path'] : '/';
205
//		$query = isset($urlParts['query']) ? $urlParts['query'] : '';
206
//		$fragment = isset($urlParts['fragment']) ? $urlParts['fragment'] : '';
207
//		$pathWithQuery = $path;
208
//		if (!empty($query)) {
209
//			$pathWithQuery .= '?' . $query;
210
//		}
211
//		if (!empty($fragment)) {
212
//			$pathWithQuery .= '#' . $fragment;
213
//		}
214
//
215
//		return $pathWithQuery;
216
//	}
217
//
218
//	/**
219
//	 * @param string $pathWithQuery
220
//	 * @param array $headers
221
//	 * @return string
222
//	 */
223
//	private function getHeaders(string $pathWithQuery, array $headers): string
224
//	{
225
//		return 'GET ' . $pathWithQuery . " HTTP/1.1\r\n"
226
//			. implode(
227
//				"\r\n",
228
//				array_map(
229
//					function ($key, $value) {
230
//						return "$key: $value";
231
//					},
232
//					array_keys($headers),
233
//					$headers
234
//				)
235
//			)
236
//			. "\r\n\r\n";
237
//	}
238
//
239
//	/**
240
//	 * @return string
241
//	 */
242
//	public function getLastOpcode(): string
243
//	{
244
//		return $this->lastOpcode;
245
//	}
246
//
247
//	/**
248
//	 * @return int
249
//	 */
250
//	public function getCloseStatus(): int
251
//	{
252
//		return $this->closeStatus;
253
//	}
254
//
255
//	/**
256
//	 * @return bool
257
//	 */
258
//	public function isConnected(): bool
259
//	{
260
//		return $this->isConnected;
261
//	}
262
//
263
//	/**
264
//	 * @param int $timeout
265
//	 * @param null $microSecs
266
//	 * @return WscMain
267
//	 */
268
//	public function setTimeout(int $timeout, $microSecs = null): WscMain
269
//	{
270
//		$this->config->setTimeout($timeout);
271
//		if ($this->socket && get_resource_type($this->socket) === 'stream') {
272
//			stream_set_timeout($this->socket, $timeout, $microSecs);
273
//		}
274
//
275
//		return $this;
276
//	}
277
//
278
//	/**
279
//	 * Sends message to opened socket connection client->server
280
//	 *
281
//	 * @param $payload
282
//	 * @param string $opcode
283
//	 * @throws \Exception
284
//	 */
285
//	public function send($payload, string $opcode = CommonsContract::EVENT_TYPE_TEXT): void
286
//	{
287
//		if (!$this->isConnected) {
288
//			$this->connect(new WebSocketConfig());
289
//		}
290
//
291
//		if (array_key_exists($opcode, self::$opcodes) === false) {
292
//			throw new BadOpcodeException(
293
//				"Bad opcode '$opcode'.  Try 'text' or 'binary'.",
294
//				CommonsContract::CLIENT_BAD_OPCODE
295
//			);
296
//		}
297
//
298
//		$payloadLength = strlen($payload);
299
//		$fragmentCursor = 0;
300
//
301
//		while ($payloadLength > $fragmentCursor) {
302
//			$subPayload = substr($payload, $fragmentCursor, $this->config->getFragmentSize());
303
//			$fragmentCursor += $this->config->getFragmentSize();
304
//			$final = $payloadLength <= $fragmentCursor;
305
//			$this->sendFragment($final, $subPayload, $opcode, true);
306
//			$opcode = 'continuation';
307
//		}
308
//	}
309
//
310
//	/**
311
//	 * Receives message client<-server
312
//	 *
313
//	 * @return null|string
314
//	 * @throws \Exception
315
//	 */
316
//	public function receive(): ?string
317
//	{
318
//		if (!$this->isConnected) {
319
//			$this->connect(new WebSocketConfig());
320
//		}
321
//
322
//		$this->hugePayload = '';
323
//
324
//		$response = null;
325
//		while ($response === null) {
326
//			$response = $this->receiveFragment();
327
//		}
328
//
329
//		return $response;
330
//	}
331
//
332
//	/**
333
//	 * Tell the socket to close.
334
//	 *
335
//	 * @param integer $status http://tools.ietf.org/html/rfc6455#section-7.4
336
//	 * @param string $message A closing message, max 125 bytes.
337
//	 * @return bool|null|string
338
//	 * @throws \Exception
339
//	 */
340
//	public function close(int $status = 1000, string $message = 'ttfn'): bool|null|string
341
//	{
342
//		$statusBin = sprintf('%016b', $status);
343
//		$statusStr = '';
344
//
345
//		foreach (str_split($statusBin, 8) as $binstr) {
346
//			$statusStr .= chr(bindec($binstr));
347
//		}
348
//
349
//		$this->send($statusStr . $message, CommonsContract::EVENT_TYPE_CLOSE);
350
//		$this->isClosing = true;
351
//
352
//		return $this->receive(); // Receiving a close frame will close the socket now.
353
//	}
354
//
355
//	/**
356
//	 * @param string $data
357
//	 * @throws ConnectionException
358
//	 */
359
//	protected function write(string $data): void
360
//	{
361
//		$written = fwrite($this->socket, $data);
362
//
363
//		if ($written < strlen($data)) {
364
//			throw new ConnectionException(
365
//				"Could only write $written out of " . strlen($data) . ' bytes.',
366
//				CommonsContract::CLIENT_COULD_ONLY_WRITE_LESS
367
//			);
368
//		}
369
//	}
370
//
371
//	/**
372
//	 * @param int $len
373
//	 * @return string
374
//	 * @throws ConnectionException
375
//	 */
376
//	protected function read(int $len): string
377
//	{
378
//		$data = '';
379
//		while (($dataLen = strlen($data)) < $len) {
380
//			$buff = fread($this->socket, $len - $dataLen);
381
//
382
//			if ($buff === false) {
383
//				$metadata = stream_get_meta_data($this->socket);
384
//				throw new ConnectionException(
385
//					'Broken frame, read ' . strlen($data) . ' of stated '
386
//					. $len . ' bytes.  Stream state: '
387
//					. json_encode($metadata),
388
//					CommonsContract::CLIENT_BROKEN_FRAME
389
//				);
390
//			}
391
//
392
//			if ($buff === '') {
393
//				$metadata = stream_get_meta_data($this->socket);
394
//				throw new ConnectionException(
395
//					'Empty read; connection dead?  Stream state: ' . json_encode($metadata),
396
//					CommonsContract::CLIENT_EMPTY_READ
397
//				);
398
//			}
399
//			$data .= $buff;
400
//		}
401
//
402
//		return $data;
403
//	}
404
//
405
//	/**
406
//	 * Helper to convert a binary to a string of '0' and '1'.
407
//	 *
408
//	 * @param string $string
409
//	 * @return string
410
//	 */
411
//	protected static function sprintB(string $string): string
412
//	{
413
//		$return = '';
414
//		$strLen = strlen($string);
415
//		for ($i = 0; $i < $strLen; $i++) {
416
//			$return .= sprintf('%08b', ord($string[$i]));
417
//		}
418
//
419
//		return $return;
420
//	}
421
//
422
//	/**
423
//	 * Sec-WebSocket-Key generator
424
//	 *
425
//	 * @return string   the 16 character length key
426
//	 * @throws \Exception
427
//	 */
428
//	private function generateKey(): string
429
//	{
430
//		$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$&/()=[]{}0123456789';
431
//		$key = '';
432
//		$chLen = strlen($chars);
433
//		for ($i = 0; $i < self::KEY_GEN_LENGTH; $i++) {
434
//			$key .= $chars[random_int(0, $chLen - 1)];
435
//		}
436
//
437
//		return base64_encode($key);
438
//	}
439
//
440
//}
441