1
|
|
|
<?php |
2
|
|
|
namespace PHPDaemon\Servers\HTTP; |
3
|
|
|
|
4
|
|
|
use PHPDaemon\Core\Daemon; |
5
|
|
|
use PHPDaemon\Core\Timer; |
6
|
|
|
use PHPDaemon\FS\FileSystem; |
7
|
|
|
use PHPDaemon\HTTPRequest\Generic; |
8
|
|
|
use PHPDaemon\HTTPRequest\Input; |
9
|
|
|
use PHPDaemon\Request\IRequestUpstream; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* @package NetworkServers |
13
|
|
|
* @subpackage Base |
14
|
|
|
* @author Vasily Zorin <[email protected]> |
15
|
|
|
*/ |
16
|
|
|
class Connection extends \PHPDaemon\Network\Connection implements IRequestUpstream |
17
|
|
|
{ |
18
|
|
|
protected $initialLowMark = 1; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* @var integer initial value of the maximum amout of bytes in buffer |
22
|
|
|
*/ |
23
|
|
|
protected $initialHighMark = 8192; |
24
|
|
|
|
25
|
|
|
protected $timeout = 45; |
26
|
|
|
|
27
|
|
|
protected $req; |
28
|
|
|
|
29
|
|
|
protected $keepaliveTimer; |
30
|
|
|
|
31
|
|
|
protected $freedBeforeProcessing = false; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @TODO DESCR |
35
|
|
|
*/ |
36
|
|
|
const STATE_FIRSTLINE = 1; |
37
|
|
|
/** |
38
|
|
|
* @TODO DESCR |
39
|
|
|
*/ |
40
|
|
|
const STATE_HEADERS = 2; |
41
|
|
|
/** |
42
|
|
|
* @TODO DESCR |
43
|
|
|
*/ |
44
|
|
|
const STATE_CONTENT = 3; |
45
|
|
|
/** |
46
|
|
|
* @TODO DESCR |
47
|
|
|
*/ |
48
|
|
|
const STATE_PROCESSING = 4; |
49
|
|
|
|
50
|
|
|
protected $EOL = "\r\n"; |
51
|
|
|
protected $currentHeader; |
52
|
|
|
|
53
|
|
|
protected $policyReqNotFound = false; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* Check if Sendfile is supported here. |
57
|
|
|
* @return boolean Succes |
58
|
|
|
*/ |
59
|
|
|
public function checkSendfileCap() |
60
|
|
|
{ |
61
|
|
|
// @todo DISCUSS |
62
|
|
|
return true; |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Check if Chunked encoding is supported here. |
67
|
|
|
* @return boolean Succes |
68
|
|
|
*/ |
69
|
|
|
public function checkChunkedEncCap() |
70
|
|
|
{ |
71
|
|
|
// @todo DISCUSS |
72
|
|
|
return true; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* @TODO |
77
|
|
|
* @return integer |
78
|
|
|
*/ |
79
|
|
|
public function getKeepaliveTimeout() |
80
|
|
|
{ |
81
|
|
|
return $this->pool->config->keepalive->value; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Read first line of HTTP request |
86
|
|
|
* @return boolean|null Success |
87
|
|
|
*/ |
88
|
|
|
protected function httpReadFirstline() |
89
|
|
|
{ |
90
|
|
|
//D($this->look(2048)); |
91
|
|
|
if (($l = $this->readline()) === null) { |
92
|
|
|
return null; |
93
|
|
|
} |
94
|
|
|
$e = explode(' ', $l); |
95
|
|
|
$u = isset($e[1]) ? parse_url($e[1]) : false; |
96
|
|
|
if ($u === false) { |
97
|
|
|
$this->badRequest($this->req); |
98
|
|
|
return false; |
99
|
|
|
} |
100
|
|
|
if (!isset($u['path'])) { |
101
|
|
|
$u['path'] = null; |
102
|
|
|
} |
103
|
|
|
if (isset($u['host'])) { |
104
|
|
|
$this->req->attrs->server['HTTP_HOST'] = $u['host']; |
105
|
|
|
} |
106
|
|
|
$srv = &$this->req->attrs->server; |
107
|
|
|
$srv['REQUEST_METHOD'] = $e[0]; |
108
|
|
|
$srv['REQUEST_TIME'] = time(); |
109
|
|
|
$srv['REQUEST_TIME_FLOAT'] = microtime(true); |
110
|
|
|
$srv['REQUEST_URI'] = $u['path'] . (isset($u['query']) ? '?' . $u['query'] : ''); |
111
|
|
|
$srv['DOCUMENT_URI'] = $u['path']; |
112
|
|
|
$srv['PHP_SELF'] = $u['path']; |
113
|
|
|
$srv['QUERY_STRING'] = isset($u['query']) ? $u['query'] : null; |
114
|
|
|
$srv['SCRIPT_NAME'] = $srv['DOCUMENT_URI'] = isset($u['path']) ? $u['path'] : '/'; |
115
|
|
|
$srv['SERVER_PROTOCOL'] = isset($e[2]) ? $e[2] : 'HTTP/1.1'; |
116
|
|
|
$srv['REMOTE_ADDR'] = $this->host; |
117
|
|
|
$srv['REMOTE_PORT'] = $this->port; |
118
|
|
|
$srv['HTTPS'] = $this->ssl ? 'on' : 'off'; |
119
|
|
|
return true; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* Read headers line-by-line |
124
|
|
|
* @return boolean|null Success |
125
|
|
|
*/ |
126
|
|
|
protected function httpReadHeaders() |
127
|
|
|
{ |
128
|
|
|
while (($l = $this->readLine()) !== null) { |
129
|
|
|
if ($l === '') { |
130
|
|
|
return true; |
131
|
|
|
} |
132
|
|
|
$e = explode(': ', $l); |
133
|
|
|
if (isset($e[1])) { |
134
|
|
|
$this->currentHeader = 'HTTP_' . strtoupper(strtr($e[0], Generic::$htr)); |
135
|
|
|
$this->req->attrs->server[$this->currentHeader] = $e[1]; |
136
|
|
View Code Duplication |
} elseif (($e[0][0] === "\t" || $e[0][0] === "\x20") && $this->currentHeader) { |
|
|
|
|
137
|
|
|
// multiline header continued |
138
|
|
|
$this->req->attrs->server[$this->currentHeader] .= $e[0]; |
139
|
|
|
} else { |
140
|
|
|
// whatever client speaks is not HTTP anymore |
141
|
|
|
$this->badRequest($this->req); |
142
|
|
|
return false; |
143
|
|
|
} |
144
|
|
|
} |
145
|
|
|
return null; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Creates new Request object |
150
|
|
|
* @return \stdClass |
151
|
|
|
*/ |
152
|
|
|
protected function newRequest() |
153
|
|
|
{ |
154
|
|
|
$req = new \stdClass; |
155
|
|
|
$req->attrs = new \stdClass(); |
156
|
|
|
$req->attrs->request = []; |
157
|
|
|
$req->attrs->get = []; |
158
|
|
|
$req->attrs->post = []; |
159
|
|
|
$req->attrs->cookie = []; |
160
|
|
|
$req->attrs->server = []; |
161
|
|
|
$req->attrs->files = []; |
162
|
|
|
$req->attrs->session = null; |
163
|
|
|
$req->attrs->paramsDone = false; |
164
|
|
|
$req->attrs->inputDone = false; |
165
|
|
|
$req->attrs->input = new Input(); |
166
|
|
|
$req->attrs->inputReaded = 0; |
167
|
|
|
$req->attrs->chunked = false; |
168
|
|
|
$req->upstream = $this; |
169
|
|
|
return $req; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* Process HTTP headers |
174
|
|
|
* @return boolean Success |
175
|
|
|
*/ |
176
|
|
|
protected function httpProcessHeaders() |
177
|
|
|
{ |
178
|
|
|
$this->req->attrs->paramsDone = true; |
179
|
|
|
if (isset($this->req->attrs->server['HTTP_CONNECTION']) |
180
|
|
|
&& preg_match('~(?:^|\W)Upgrade(?:\W|$)~i', $this->req->attrs->server['HTTP_CONNECTION']) |
181
|
|
|
&& isset($this->req->attrs->server['HTTP_UPGRADE']) |
182
|
|
|
&& (strtolower($this->req->attrs->server['HTTP_UPGRADE']) === 'websocket') |
183
|
|
|
) { |
184
|
|
|
if ($this->pool->WS) { |
185
|
|
|
$this->pool->WS->inheritFromRequest($this->req, $this); |
186
|
|
|
} |
187
|
|
|
return false; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
$this->req = Daemon::$appResolver->getRequest( |
191
|
|
|
$this->req, |
192
|
|
|
$this, |
193
|
|
|
isset($this->pool->config->responder->value) ? $this->pool->config->responder->value : null |
194
|
|
|
); |
195
|
|
|
|
196
|
|
|
if ($this->req instanceof \stdClass) { |
197
|
|
|
$this->endRequest($this->req, 0, 0); |
198
|
|
|
return false; |
199
|
|
|
} else { |
200
|
|
|
if ($this->pool->config->sendfile->value |
201
|
|
|
&& (!$this->pool->config->sendfileonlybycommand->value || isset($this->req->attrs->server['USE_SENDFILE'])) |
202
|
|
|
&& !isset($this->req->attrs->server['DONT_USE_SENDFILE']) |
203
|
|
|
) { |
204
|
|
|
$req = $this->req; |
205
|
|
|
|
206
|
|
|
FileSystem::tempnam( |
207
|
|
|
$this->pool->config->sendfiledir->value, |
208
|
|
|
$this->pool->config->sendfileprefix->value, |
209
|
|
|
function ($fn) use ($req) { |
210
|
|
|
FileSystem::open($fn, 'wb', function ($file) use ($req) { |
211
|
|
|
$req->sendfp = $file; |
212
|
|
|
}); |
213
|
|
|
$req->header('X-Sendfile: ' . $fn); |
214
|
|
|
} |
215
|
|
|
); |
216
|
|
|
} |
217
|
|
|
$this->req->callInit(); |
218
|
|
|
} |
219
|
|
|
return true; |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
/* Used for debugging protocol issues */ |
223
|
|
|
/*public function readline() { |
224
|
|
|
$s = parent::readline(); |
225
|
|
|
Daemon::log(Debug::json($s)); |
226
|
|
|
return $s; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
public function write($s) { |
230
|
|
|
Daemon::log(Debug::json($s)); |
231
|
|
|
parent::write($s); |
232
|
|
|
}* |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* Called when new data received. |
236
|
|
|
* @return void |
237
|
|
|
*/ |
238
|
|
|
|
239
|
|
|
/** |
240
|
|
|
* onRead |
241
|
|
|
* @return void |
242
|
|
|
*/ |
243
|
|
|
protected function onRead() |
244
|
|
|
{ |
245
|
|
View Code Duplication |
if (!$this->policyReqNotFound) { |
|
|
|
|
246
|
|
|
$d = $this->drainIfMatch("<policy-file-request/>\x00"); |
247
|
|
|
if ($d === null) { // partially match |
248
|
|
|
return; |
249
|
|
|
} |
250
|
|
|
if ($d) { |
251
|
|
|
$FP = \PHPDaemon\Servers\FlashPolicy\Pool::getInstance( |
252
|
|
|
$this->pool->config->fpsname->value, |
253
|
|
|
false |
254
|
|
|
); |
255
|
|
|
if ($FP && $FP->policyData |
256
|
|
|
) { |
257
|
|
|
$this->write($FP->policyData . "\x00"); |
258
|
|
|
} |
259
|
|
|
$this->finish(); |
260
|
|
|
return; |
261
|
|
|
} else { |
262
|
|
|
$this->policyReqNotFound = true; |
263
|
|
|
} |
264
|
|
|
} |
265
|
|
|
start: |
266
|
|
|
if ($this->finished) { |
267
|
|
|
return; |
268
|
|
|
} |
269
|
|
|
if ($this->state === self::STATE_ROOT) { |
270
|
|
|
if ($this->req !== null) { // we have to wait the current request |
271
|
|
|
return; |
272
|
|
|
} |
273
|
|
|
if (!$this->req = $this->newRequest()) { |
274
|
|
|
$this->finish(); |
275
|
|
|
return; |
276
|
|
|
} |
277
|
|
|
$this->state = self::STATE_FIRSTLINE; |
278
|
|
|
} else { |
279
|
|
|
if (!$this->req || $this->state === self::STATE_PROCESSING) { |
280
|
|
|
if (isset($this->bev) && ($this->bev->input->length > 0)) { |
281
|
|
|
Daemon::log('Unexpected input (HTTP request, ' . $this->state . '): ' . json_encode($this->read($this->bev->input->length))); |
282
|
|
|
} |
283
|
|
|
return; |
284
|
|
|
} |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
if ($this->state === self::STATE_FIRSTLINE) { |
288
|
|
|
if (!$this->httpReadFirstline()) { |
289
|
|
|
return; |
290
|
|
|
} |
291
|
|
|
Timer::remove($this->keepaliveTimer); |
292
|
|
|
$this->state = self::STATE_HEADERS; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
View Code Duplication |
if ($this->state === self::STATE_HEADERS) { |
|
|
|
|
296
|
|
|
if (!$this->httpReadHeaders()) { |
297
|
|
|
return; |
298
|
|
|
} |
299
|
|
|
if (!$this->httpProcessHeaders()) { |
300
|
|
|
$this->finish(); |
301
|
|
|
return; |
302
|
|
|
} |
303
|
|
|
$this->state = self::STATE_CONTENT; |
304
|
|
|
} |
305
|
|
|
if ($this->state === self::STATE_CONTENT) { |
306
|
|
|
if (!isset($this->req->attrs->input) || !$this->req->attrs->input) { |
307
|
|
|
$this->finish(); |
308
|
|
|
return; |
309
|
|
|
} |
310
|
|
|
$this->req->attrs->input->readFromBuffer($this->bev->input); |
311
|
|
|
if (!$this->req->attrs->input->isEOF()) { |
312
|
|
|
return; |
313
|
|
|
} |
314
|
|
|
$this->state = self::STATE_PROCESSING; |
315
|
|
|
if ($this->freedBeforeProcessing) { |
316
|
|
|
$this->freeRequest($this->req); |
317
|
|
|
$this->freedBeforeProcessing = false; |
318
|
|
|
goto start; |
319
|
|
|
} |
320
|
|
|
$this->freezeInput(); |
321
|
|
|
if ($this->req->attrs->inputDone && $this->req->attrs->paramsDone) { |
322
|
|
|
if ($this->pool->variablesOrder === null) { |
323
|
|
|
$this->req->attrs->request = $this->req->attrs->get + $this->req->attrs->post + $this->req->attrs->cookie; |
324
|
|
|
} else { |
325
|
|
|
for ($i = 0, $s = mb_orig_strlen($this->pool->variablesOrder); $i < $s; ++$i) { |
326
|
|
|
$char = $this->pool->variablesOrder[$i]; |
327
|
|
|
if ($char === 'G') { |
328
|
|
View Code Duplication |
if (is_array($this->req->attrs->get)) { |
|
|
|
|
329
|
|
|
$this->req->attrs->request += $this->req->attrs->get; |
330
|
|
|
} |
331
|
|
View Code Duplication |
} elseif ($char === 'P') { |
|
|
|
|
332
|
|
|
if (is_array($this->req->attrs->post)) { |
333
|
|
|
$this->req->attrs->request += $this->req->attrs->post; |
334
|
|
|
} |
335
|
|
|
} elseif ($char === 'C') { |
336
|
|
View Code Duplication |
if (is_array($this->req->attrs->cookie)) { |
|
|
|
|
337
|
|
|
$this->req->attrs->request += $this->req->attrs->cookie; |
338
|
|
|
} |
339
|
|
|
} |
340
|
|
|
} |
341
|
|
|
} |
342
|
|
|
Daemon::$process->timeLastActivity = time(); |
343
|
|
|
} |
344
|
|
|
} |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
/** |
348
|
|
|
* Handles the output from downstream requests. |
349
|
|
|
* @param object $req \PHPDaemon\Request\Generic. |
350
|
|
|
* @param string $s The output. |
351
|
|
|
* @return boolean Success |
352
|
|
|
*/ |
353
|
|
|
public function requestOut($req, $s) |
354
|
|
|
{ |
355
|
|
|
if ($this->write($s) === false) { |
356
|
|
|
$req->abort(); |
357
|
|
|
return false; |
358
|
|
|
} |
359
|
|
|
return true; |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
/** |
363
|
|
|
* End request |
364
|
|
|
* @return void |
365
|
|
|
*/ |
366
|
|
|
public function endRequest($req, $appStatus, $protoStatus) |
367
|
|
|
{ |
368
|
|
|
if ($protoStatus === -1) { |
369
|
|
|
$this->close(); |
370
|
|
|
} else { |
371
|
|
|
if ($req->attrs->chunked) { |
372
|
|
|
$this->write("0\r\n\r\n"); |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
if (isset($req->keepalive) && $req->keepalive && $this->pool->config->keepalive->value) { |
376
|
|
|
$this->keepaliveTimer = setTimeout(function ($timer) { |
|
|
|
|
377
|
|
|
$this->finish(); |
378
|
|
|
}, $this->pool->config->keepalive->value); |
379
|
|
|
} else { |
380
|
|
|
$this->finish(); |
381
|
|
|
} |
382
|
|
|
} |
383
|
|
|
$this->freeRequest($req); |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
/** |
387
|
|
|
* Frees this request |
388
|
|
|
* @return void |
389
|
|
|
*/ |
390
|
|
|
public function freeRequest($req) |
391
|
|
|
{ |
392
|
|
|
if ($this->state !== self::STATE_PROCESSING) { |
393
|
|
|
$this->freedBeforeProcessing = true; |
394
|
|
|
return; |
395
|
|
|
} |
396
|
|
|
$req->attrs->input = null; |
397
|
|
|
$this->req = null; |
398
|
|
|
$this->state = self::STATE_ROOT; |
399
|
|
|
$this->unfreezeInput(); |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
/** |
403
|
|
|
* Called when connection is finished |
404
|
|
|
* @return void |
405
|
|
|
*/ |
406
|
|
|
public function onFinish() |
407
|
|
|
{ |
408
|
|
|
Timer::remove($this->keepaliveTimer); |
409
|
|
|
if ($this->req !== null && $this->req instanceof Generic) { |
410
|
|
|
if (!$this->req->isFinished()) { |
411
|
|
|
$this->req->abort(); |
412
|
|
|
} |
413
|
|
|
} |
414
|
|
|
$this->req = null; |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
/** |
418
|
|
|
* Send Bad request |
419
|
|
|
* @return void |
420
|
|
|
*/ |
421
|
|
|
public function badRequest($req) |
422
|
|
|
{ |
423
|
|
|
$this->state = self::STATE_ROOT; |
424
|
|
|
$this->write("400 Bad Request\r\n\r\n<html><head><title>400 Bad Request</title></head><body bgcolor=\"white\"><center><h1>400 Bad Request</h1></center></body></html>"); |
425
|
|
|
$this->finish(); |
426
|
|
|
} |
427
|
|
|
} |
428
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.