forked from phacility/phabricator
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPhabricatorStreamingProtocolAdapter.php
170 lines (132 loc) · 4.3 KB
/
PhabricatorStreamingProtocolAdapter.php
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
159
160
161
162
163
164
165
166
167
168
169
170
<?php
abstract class PhabricatorStreamingProtocolAdapter
extends PhabricatorProtocolAdapter {
protected $readHandles;
protected $multiHandle;
protected $authtoken;
private $readBuffers;
private $server;
private $active;
private $inRooms = array();
public function getServiceName() {
$uri = new PhutilURI($this->server);
return $uri->getDomain();
}
public function connect() {
$this->server = $this->getConfig('server');
$this->authtoken = $this->getConfig('authtoken');
$rooms = $this->getConfig('join');
// First, join the room
if (!$rooms) {
throw new Exception(pht('Not configured to join any rooms!'));
}
$this->readBuffers = array();
// Set up our long poll in a curl multi request so we can
// continue running while it executes in the background
$this->multiHandle = curl_multi_init();
$this->readHandles = array();
foreach ($rooms as $room_id) {
$this->joinRoom($room_id);
// Set up the curl stream for reading
$url = $this->buildStreamingUrl($room_id);
$ch = $this->readHandles[$url] = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt(
$ch,
CURLOPT_USERPWD,
$this->authtoken.':x');
curl_setopt(
$ch,
CURLOPT_HTTPHEADER,
array('Content-type: application/json'));
curl_setopt(
$ch,
CURLOPT_WRITEFUNCTION,
array($this, 'read'));
curl_setopt($ch, CURLOPT_BUFFERSIZE, 128);
curl_setopt($ch, CURLOPT_TIMEOUT, 0);
curl_multi_add_handle($this->multiHandle, $ch);
// Initialize read buffer
$this->readBuffers[$url] = '';
}
$this->active = null;
$this->blockingMultiExec();
}
protected function joinRoom($room_id) {
// Optional hook, by default, do nothing
}
// This is our callback for the background curl multi-request.
// Puts the data read in on the readBuffer for processing.
private function read($ch, $data) {
$info = curl_getinfo($ch);
$length = strlen($data);
$this->readBuffers[$info['url']] .= $data;
return $length;
}
private function blockingMultiExec() {
do {
$status = curl_multi_exec($this->multiHandle, $this->active);
} while ($status == CURLM_CALL_MULTI_PERFORM);
// Check for errors
if ($status != CURLM_OK) {
throw new Exception(
pht('Phabricator Bot had a problem reading from stream.'));
}
}
public function getNextMessages($poll_frequency) {
$messages = array();
if (!$this->active) {
throw new Exception(pht('Phabricator Bot stopped reading from stream.'));
}
// Prod our http request
curl_multi_select($this->multiHandle, $poll_frequency);
$this->blockingMultiExec();
// Process anything waiting on the read buffer
while ($m = $this->processReadBuffer()) {
$messages[] = $m;
}
return $messages;
}
private function processReadBuffer() {
foreach ($this->readBuffers as $url => &$buffer) {
$until = strpos($buffer, "}\r");
if ($until == false) {
continue;
}
$message = substr($buffer, 0, $until + 1);
$buffer = substr($buffer, $until + 2);
$m_obj = phutil_json_decode($message);
if ($message = $this->processMessage($m_obj)) {
return $message;
}
}
// If we're here, there's nothing to process
return false;
}
protected function performPost($endpoint, $data = null) {
$uri = new PhutilURI($this->server);
$uri->setPath($endpoint);
$payload = json_encode($data);
list($output) = id(new HTTPSFuture($uri))
->setMethod('POST')
->addHeader('Content-Type', 'application/json')
->addHeader('Authorization', $this->getAuthorizationHeader())
->setData($payload)
->resolvex();
$output = trim($output);
if (strlen($output)) {
return phutil_json_decode($output);
}
return true;
}
protected function getAuthorizationHeader() {
return 'Basic '.$this->getEncodedAuthToken();
}
protected function getEncodedAuthToken() {
return base64_encode($this->authtoken.':x');
}
abstract protected function buildStreamingUrl($channel);
abstract protected function processMessage(array $raw_object);
}