StreamRunner::emitBodyRange()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 11
c 1
b 0
f 0
nc 4
nop 2
dl 0
loc 20
rs 9.9
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Furious\HttpRunner;
6
7
use Psr\Http\Message\ResponseInterface;
8
use Psr\Http\Message\StreamInterface;
9
use function flush;
10
11
final class StreamRunner implements RunnerInterface
12
{
13
    private Checker $checker;
14
    private Emitter $emitter;
15
    private int $maxBufferLength;
16
17
    public function __construct(int $maxBufferLength = 8192)
18
    {
19
        $this->checker = new Checker();
20
        $this->emitter = new Emitter();
21
        $this->maxBufferLength = $maxBufferLength;
22
    }
23
24
    public function run(ResponseInterface $response) : bool
25
    {
26
        $this->checker->checkHeadersAlreadySent();
27
        $this->checker->checkOutputAlreadyWrote();
28
29
        $this->emitter->emitHeaders($response);
30
        $this->emitter->emitStatusLine($response);
31
32
        flush();
33
34
        $range = $this->parseContentRange($response->getHeaderLine('Content-Range'));
35
36
        if (null === $range or 'bytes' !== $range[0]) {
37
            $this->emitBody($response);
38
            return true;
39
        }
40
41
        $this->emitBodyRange($range, $response);
42
        return true;
43
    }
44
45
    private function emitBody(ResponseInterface $response) : void
46
    {
47
        $body = $response->getBody();
48
49
        if ($body->isSeekable()) {
50
            $body->rewind();
51
        }
52
53
        if (!$body->isReadable()) {
54
            echo $body;
55
            return;
56
        }
57
58
        while (!$body->eof()) {
59
            echo $body->read($this->maxBufferLength);
60
        }
61
    }
62
63
    private function emitBodyRange(array $range, ResponseInterface $response): void
64
    {
65
        list($unit, $first, $last, $length) = $range;
66
67
        $body = $response->getBody();
68
        $length = $last - $first + 1;
69
70
        if ($body->isSeekable()) {
71
            $body->seek($first);
72
            $first = 0;
73
        }
74
75
        if (!$body->isReadable()) {
76
            echo substr($body->getContents(), $first, $length);
77
            return;
78
        }
79
80
        $remaining = $length;
81
82
        $this->emitBodyRemaining($body, $remaining);
83
    }
84
85
    private function emitBodyRemaining(StreamInterface $body, int $remaining): void
86
    {
87
        while ($remaining >= $this->maxBufferLength and !$body->eof()) {
88
            $contents = $body->read($this->maxBufferLength);
89
            $remaining -= strlen($contents);
90
            echo $contents;
91
        }
92
93
        if ($remaining > 0 and !$body->eof()) {
94
            echo $body->read($remaining);
95
        }
96
    }
97
98
    private function parseContentRange(string $header) : ?array
99
    {
100
        if (!preg_match('/(?P<unit>[\w]+)\s+(?P<first>\d+)-(?P<last>\d+)\/(?P<length>\d+|\*)/', $header, $matches)) {
101
            return null;
102
        }
103
104
        return [
105
            $matches['unit'],
106
            (int) $matches['first'],
107
            (int) $matches['last'],
108
            $matches['length'] === '*' ? '*' : (int) $matches['length'],
109
        ];
110
    }
111
}