Completed
Pull Request — master (#234)
by Дмитрий
07:51
created

Connection::httpReadHeaders()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 21
Code Lines 14

Duplication

Lines 4
Ratio 19.05 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 7
dl 4
loc 21
rs 7.551
eloc 14
c 1
b 1
f 0
nc 5
nop 0
1
<?php
2
namespace PHPDaemon\Servers\HTTP;
3
4
use PHPDaemon\Core\Daemon;
5
use PHPDaemon\Core\Debug;
6
use PHPDaemon\Core\Timer;
7
use PHPDaemon\FS\FileSystem;
8
use PHPDaemon\HTTPRequest\Generic;
9
use PHPDaemon\HTTPRequest\Input;
10
use PHPDaemon\Request\IRequestUpstream;
11
12
/**
13
 * @package    NetworkServers
14
 * @subpackage Base
15
 * @author     Vasily Zorin <[email protected]>
16
 */
17
class Connection extends \PHPDaemon\Network\Connection implements IRequestUpstream
18
{
19
20
    protected $initialLowMark = 1;
21
22
    /**
23
     * @var integer initial value of the maximum amout of bytes in buffer
24
     */
25
    protected $initialHighMark = 8192;
26
27
    protected $timeout = 45;
28
29
    protected $req;
30
31
    protected $keepaliveTimer;
32
33
    protected $freedBeforeProcessing = false;
34
35
    /**
36
     * @TODO DESCR
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
37
     */
38
    const STATE_FIRSTLINE  = 1;
39
    /**
40
     * @TODO DESCR
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
41
     */
42
    const STATE_HEADERS    = 2;
43
    /**
44
     * @TODO DESCR
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
45
     */
46
    const STATE_CONTENT    = 3;
47
    /**
48
     * @TODO DESCR
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
49
     */
50
    const STATE_PROCESSING = 4;
51
52
    protected $EOL = "\r\n";
53
    protected $currentHeader;
54
55
    protected $policyReqNotFound = false;
56
57
    /**
58
     * Check if Sendfile is supported here.
59
     * @return boolean Succes
60
     */
61
    public function checkSendfileCap()
62
    { // @DISCUSS
63
        return true;
64
    }
65
66
    /**
67
     * Check if Chunked encoding is supported here.
68
     * @return boolean Succes
69
     */
70
    public function checkChunkedEncCap()
71
    { // @DISCUSS
72
        return true;
73
    }
74
75
    /**
76
     * @TODO
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
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));
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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 (
180
                isset($this->req->attrs->server['HTTP_CONNECTION']) && preg_match('~(?:^|\W)Upgrade(?:\W|$)~i', $this->req->attrs->server['HTTP_CONNECTION'])
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 157 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
181
                && isset($this->req->attrs->server['HTTP_UPGRADE']) && (strtolower($this->req->attrs->server['HTTP_UPGRADE']) === 'websocket')
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 142 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
182
        ) {
183
            if ($this->pool->WS) {
184
                $this->pool->WS->inheritFromRequest($this->req, $this);
185
            }
186
            return false;
187
        }
188
189
        $this->req = Daemon::$appResolver->getRequest($this->req, $this, isset($this->pool->config->responder->value) ? $this->pool->config->responder->value : null);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 166 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
190
        if ($this->req instanceof \stdClass) {
191
            $this->endRequest($this->req, 0, 0);
192
            return false;
193
        } else {
194
            if ($this->pool->config->sendfile->value && (!$this->pool->config->sendfileonlybycommand->value || isset($this->req->attrs->server['USE_SENDFILE']))
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 160 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
195
                    && !isset($this->req->attrs->server['DONT_USE_SENDFILE'])
196
            ) {
197
                $req = $this->req;
198
                FileSystem::tempnam($this->pool->config->sendfiledir->value, $this->pool->config->sendfileprefix->value, function ($fn) use ($req) {
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 148 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
199
                    FileSystem::open($fn, 'wb', function ($file) use ($req) {
200
                        $req->sendfp = $file;
201
                    });
202
                    $req->header('X-Sendfile: ' . $fn);
203
                });
204
            }
205
            $this->req->callInit();
206
        }
207
        return true;
208
    }
209
210
    /* Used for debugging protocol issues */
211
    /*public function readline() {
0 ignored issues
show
Unused Code Comprehensibility introduced by
49% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
212
        $s = parent::readline();
213
        Daemon::log(Debug::json($s));
214
        return $s;
215
    }
216
217
    public function write($s) {
218
        Daemon::log(Debug::json($s));
219
        parent::write($s);
220
    }*
221
222
    /**
223
     * Called when new data received.
224
     * @return void
225
     */
226
227
    /**
228
     * onRead
229
     * @return void
230
     */
231
    protected function onRead()
232
    {
233 View Code Duplication
        if (!$this->policyReqNotFound) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
234
            $d = $this->drainIfMatch("<policy-file-request/>\x00");
235
            if ($d === null) { // partially match
236
                return;
237
            }
238
            if ($d) {
239
                if (($FP = \PHPDaemon\Servers\FlashPolicy\Pool::getInstance($this->pool->config->fpsname->value, false)) && $FP->policyData) {
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 142 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
240
                    $this->write($FP->policyData . "\x00");
241
                }
242
                $this->finish();
243
                return;
244
            } else {
245
                $this->policyReqNotFound = true;
246
            }
247
        }
248
        start:
249
        if ($this->finished) {
250
            return;
251
        }
252
        if ($this->state === self::STATE_ROOT) {
253
            if ($this->req !== null) { // we have to wait the current request
254
                return;
255
            }
256
            if (!$this->req = $this->newRequest()) {
257
                $this->finish();
258
                return;
259
            }
260
            $this->state = self::STATE_FIRSTLINE;
261
        } else {
262
            if (!$this->req || $this->state === self::STATE_PROCESSING) {
263
                if (isset($this->bev) && ($this->bev->input->length > 0)) {
264
                    Daemon::log('Unexpected input (HTTP request, ' . $this->state . '): ' . json_encode($this->read($this->bev->input->length)));
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 145 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
265
                }
266
                return;
267
            }
268
        }
269
270
        if ($this->state === self::STATE_FIRSTLINE) {
271
            if (!$this->httpReadFirstline()) {
272
                return;
273
            }
274
            Timer::remove($this->keepaliveTimer);
275
            $this->state = self::STATE_HEADERS;
276
        }
277
278 View Code Duplication
        if ($this->state === self::STATE_HEADERS) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
279
            if (!$this->httpReadHeaders()) {
280
                return;
281
            }
282
            if (!$this->httpProcessHeaders()) {
283
                $this->finish();
284
                return;
285
            }
286
            $this->state = self::STATE_CONTENT;
287
        }
288
        if ($this->state === self::STATE_CONTENT) {
289
            if (!isset($this->req->attrs->input) || !$this->req->attrs->input) {
290
                $this->finish();
291
                return;
292
            }
293
            $this->req->attrs->input->readFromBuffer($this->bev->input);
294
            if (!$this->req->attrs->input->isEOF()) {
295
                return;
296
            }
297
            $this->state = self::STATE_PROCESSING;
298
            if ($this->freedBeforeProcessing) {
299
                $this->freeRequest($this->req);
300
                $this->freedBeforeProcessing = false;
301
                goto start;
302
            }
303
            $this->freezeInput();
304
            if ($this->req->attrs->inputDone && $this->req->attrs->paramsDone) {
305
                if ($this->pool->variablesOrder === null) {
306
                    $this->req->attrs->request = $this->req->attrs->get + $this->req->attrs->post + $this->req->attrs->cookie;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 126 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
307
                } else {
308
                    for ($i = 0, $s = mb_orig_strlen($this->pool->variablesOrder); $i < $s; ++$i) {
309
                        $char = $this->pool->variablesOrder[$i];
310
                        if ($char === 'G') {
311 View Code Duplication
                            if (is_array($this->req->attrs->get)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
312
                                $this->req->attrs->request += $this->req->attrs->get;
313
                            }
314 View Code Duplication
                        } elseif ($char === 'P') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
315
                            if (is_array($this->req->attrs->post)) {
316
                                $this->req->attrs->request += $this->req->attrs->post;
317
                            }
318
                        } elseif ($char === 'C') {
319 View Code Duplication
                            if (is_array($this->req->attrs->cookie)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
320
                                $this->req->attrs->request += $this->req->attrs->cookie;
321
                            }
322
                        }
323
                    }
324
                }
325
                Daemon::$process->timeLastActivity = time();
326
            }
327
        }
328
    }
329
330
    /**
331
     * Handles the output from downstream requests.
332
     * @param  object  $req \PHPDaemon\Request\Generic.
333
     * @param  string  $s   The output.
334
     * @return boolean      Success
335
     */
336
    public function requestOut($req, $s)
337
    {
338
        if ($this->write($s) === false) {
339
            $req->abort();
340
            return false;
341
        }
342
        return true;
343
    }
344
345
    /**
346
     * End request
347
     * @return void
348
     */
349
    public function endRequest($req, $appStatus, $protoStatus)
350
    {
351
        if ($protoStatus === -1) {
352
            $this->close();
353
        } else {
354
            if ($req->attrs->chunked) {
355
                $this->write("0\r\n\r\n");
356
            }
357
358
            if (isset($req->keepalive) && $req->keepalive && $this->pool->config->keepalive->value) {
359
                $this->keepaliveTimer = setTimeout(function ($timer) {
0 ignored issues
show
Unused Code introduced by
The parameter $timer is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
360
                    $this->finish();
361
                }, $this->pool->config->keepalive->value);
362
            } else {
363
                $this->finish();
364
            }
365
        }
366
        $this->freeRequest($req);
367
    }
368
369
    /**
370
     * Frees this request
371
     * @return void
372
     */
373
    public function freeRequest($req)
374
    {
375
        if ($this->state !== self::STATE_PROCESSING) {
376
            $this->freedBeforeProcessing = true;
377
            return;
378
        }
379
        $req->attrs->input = null;
380
        $this->req   = null;
381
        $this->state = self::STATE_ROOT;
382
        $this->unfreezeInput();
383
    }
384
385
    /**
386
     * Called when connection is finished
387
     * @return void
388
     */
389
    public function onFinish()
390
    {
391
        Timer::remove($this->keepaliveTimer);
392
        if ($this->req !== null && $this->req instanceof Generic) {
393
            if (!$this->req->isFinished()) {
394
                $this->req->abort();
395
            }
396
        }
397
        $this->req = null;
398
    }
399
400
    /**
401
     * Send Bad request
402
     * @return void
403
     */
404
    public function badRequest($req)
405
    {
406
        $this->state = self::STATE_ROOT;
407
        $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>");
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 176 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
408
        $this->finish();
409
    }
410
}
411