Completed
Push — master ( 1b2d0a...89ea1a )
by Oscar
03:00
created

ReadResponse::continueOnError()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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