Completed
Push — master ( 01eb59...230b2e )
by Oscar
02:45
created

ReadResponse::readFile()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 16
rs 9.4285
cc 3
eloc 8
nc 3
nop 2
1
<?php
2
3
namespace Psr7Middlewares\Middleware;
4
5
use Psr7Middlewares\Utils;
6
use Psr\Http\Message\ServerRequestInterface;
7
use Psr\Http\Message\ResponseInterface;
8
9
/**
10
 * Middleware to read the response.
11
 */
12
class ReadResponse
13
{
14
    use Utils\FileTrait;
15
    use Utils\StreamTrait;
16
17
    private $continueOnError = false;
18
19
    /**
20
     * Configure if continue to the next middleware if the response has not found.
21
     * 
22
     * @param bool $continueOnError
23
     * 
24
     * @return self
25
     */
26
    public function continueOnError($continueOnError = true)
27
    {
28
        $this->continueOnError = $continueOnError;
29
30
        return $this;
31
    }
32
33
    /**
34
     * Execute the middleware.
35
     *
36
     * @param ServerRequestInterface $request
37
     * @param ResponseInterface      $response
38
     * @param callable               $next
39
     *
40
     * @return ResponseInterface
41
     */
42
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
43
    {
44
        //If the method is not allowed
45
        if ($request->getMethod() !== 'GET') {
46
            if ($this->continueOnError) {
47
                return $next($request, $response);
48
            }
49
50
            return $response->withStatus(405);
51
        }
52
53
        $file = $this->getFilename($request);
54
55
        //If the file does not exists, check if is gzipped
56
        if (!is_file($file)) {
57
            $file .= '.gz';
58
59
            if (EncodingNegotiator::getEncoding($request) !== 'gzip' || !is_file($file)) {
60
                if ($this->continueOnError) {
61
                    return $next($request, $response);
62
                }
63
64
                return $response->withStatus(404);
65
            }
66
67
            $response = $response->withHeader('Content-Encoding', 'gzip');
68
        }
69
70
        //Handle range header
71
        $response = $this->range($request, $response->withBody(self::createStream($file, 'r')));
72
73
        if ($this->continueOnError) {
74
            return $response;
75
        }
76
77
        return $next($request, $response);
78
    }
79
80
    /**
81
     * Handle range requests.
82
     * 
83
     * @param ServerRequestInterface $request
84
     * @param ResponseInterface      $response
85
     * 
86
     * @return ResponseInterface
87
     */
88
    private static function range(ServerRequestInterface $request, ResponseInterface $response)
89
    {
90
        $response = $response->withHeader('Accept-Ranges', 'bytes');
91
92
        $range = $request->getHeaderLine('Range');
93
94
        if (empty($range) || !($range = self::parseRangeHeader($range))) {
95
            return $response;
96
        }
97
98
        list($first, $last) = $range;
99
        $size = $response->getBody()->getSize();
100
101
        if ($last === null) {
102
            $last = $size - 1;
103
        }
104
105
        return $response
106
            ->withStatus(206)
107
            ->withHeader('Content-Length', (string) ($last - $first + 1))
108
            ->withHeader('Content-Range', sprintf('bytes %d-%d/%d', $first, $last, $size));
109
    }
110
111
    /**
112
     * Parses a range header, for example: bytes=500-999.
113
     *
114
     * @param string $header
115
     *
116
     * @return false|array [first, last]
117
     */
118
    private static function parseRangeHeader($header)
119
    {
120
        if (preg_match('/bytes=(?P<first>\d+)-(?P<last>\d+)?/', $header, $matches)) {
121
            return [
122
                (int) $matches['first'],
123
                isset($matches['last']) ? (int) $matches['last'] : null,
124
            ];
125
        }
126
127
        return false;
128
    }
129
}
130