Stream.php
3.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
<?php
/**
* Plugin controller.
*
* @copyright (c) 2021, Code Atlantic LLC.
*
* @package ContentControl
*/
namespace ContentControl\Base;
defined( 'ABSPATH' ) || exit;
/**
* HTTP Stream class.
*/
class Stream {
/**
* Stream name.
*
* @var string
*/
protected $stream_name;
/**
* Version.
*
* @var string
*/
const VERSION = '1.0.0';
/**
* Stream constructor.
*
* @param string $stream_name Stream name.
*/
public function __construct( $stream_name = 'stream' ) {
$this->stream_name = $stream_name;
}
/**
* Start SSE stream.
*
* @return void
*/
public function start() {
if ( headers_sent() ) {
// Do not start the stream if headers have already been sent.
return;
}
// Disable default disconnect checks.
ignore_user_abort( true );
// phpcs:disable WordPress.PHP.IniSet.Risky, WordPress.PHP.NoSilencedErrors.Discouraged
@ini_set( 'zlib.output_compression', '0' );
@ini_set( 'implicit_flush', '1' );
@ini_set( 'log_limit', '8096' );
@ob_end_clean();
set_time_limit( 0 );
// phpcs:enable WordPress.PHP.IniSet.Risky, WordPress.PHP.NoSilencedErrors.Discouraged
$this->send_headers();
}
/**
* Send SSE headers.
*
* @return void
*/
public function send_headers() {
header( 'Content-Type: text/event-stream' );
header( 'Stream-Name: ' . $this->stream_name );
header( 'Cache-Control: no-cache' );
header( 'Connection: keep-alive' );
// Nginx: unbuffered responses suitable for Comet and HTTP streaming applications.
header( 'X-Accel-Buffering: no' );
$this->flush_buffers();
}
/**
* Flush buffers.
*
* Uses a micro delay to prevent the stream from flushing too quickly.
*
* @return void
*/
protected function flush_buffers() {
// This is for the buffer achieve the minimum size in order to flush data.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo str_repeat( ' ', 1024 * 8 ) . PHP_EOL;
flush(); // Unless both are called. Some browsers will still cache.
// Neccessary to prevent the stream from flushing too quickly.
usleep( 1000 );
}
/**
* Send general message/data to the client.
*
* @param mixed $data Data to send.
*
* @return void
*/
public function send_data( $data ) {
$data = is_string( $data ) ? $data : \wp_json_encode( $data );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo "data: {$data}" . PHP_EOL;
echo PHP_EOL;
$this->flush_buffers();
}
/**
* Send an event to the client.
*
* @param string $event Event name.
* @param mixed $data Data to send.
*
* @return void
*/
public function send_event( $event, $data = '' ) {
$data = is_string( $data ) ? $data : \wp_json_encode( $data );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo "event: {$event}" . PHP_EOL;
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo "data: {$data}" . PHP_EOL;
echo PHP_EOL;
$this->flush_buffers();
}
/**
* Send an error to the client.
*
* @param array{message:string}|string $error Error message.
*
* @return void
*/
public function send_error( $error ) {
$this->send_event( 'error', $error );
}
/**
* Check if the connection should abort.
*
* @return bool
*/
public function should_abort() {
return (bool) connection_aborted();
}
}