Completed
Push — master ( 96c43b...fcf598 )
by Tobias
03:54
created

DecoderPlugin   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 120
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 92.86%

Importance

Changes 0
Metric Value
wmc 14
lcom 1
cbo 6
dl 0
loc 120
ccs 39
cts 42
cp 0.9286
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A decorateStream() 0 16 4
A __construct() 0 11 1
A handleRequest() 0 14 3
A decodeResponse() 0 10 2
B decodeOnEncodingHeader() 0 23 4
1
<?php
2
3
namespace Http\Client\Common\Plugin;
4
5
use Http\Client\Common\Plugin;
6
use Http\Message\Encoding;
7
use Psr\Http\Message\RequestInterface;
8
use Psr\Http\Message\ResponseInterface;
9
use Psr\Http\Message\StreamInterface;
10
use Symfony\Component\OptionsResolver\OptionsResolver;
11
12
/**
13
 * Allow to decode response body with a chunk, deflate, compress or gzip encoding.
14
 *
15
 * If zlib is not installed, only chunked encoding can be handled.
16
 *
17
 * If Content-Encoding is not disabled, the plugin will add an Accept-Encoding header for the encoding methods it supports.
18
 *
19
 * @author Joel Wurtz <[email protected]>
20
 */
21
final class DecoderPlugin implements Plugin
22
{
23
    /**
24
     * @var bool Whether this plugin decode stream with value in the Content-Encoding header (default to true).
25
     *
26
     * If set to false only the Transfer-Encoding header will be used
27
     */
28
    private $useContentEncoding;
29
30
    /**
31
     * @param array $config {
32
     *
33
     *    @var bool $use_content_encoding Whether this plugin should look at the Content-Encoding header first or only at the Transfer-Encoding (defaults to true).
34
     * }
35
     */
36 6
    public function __construct(array $config = [])
37
    {
38 6
        $resolver = new OptionsResolver();
39 6
        $resolver->setDefaults([
40 6
            'use_content_encoding' => true,
41
        ]);
42 6
        $resolver->setAllowedTypes('use_content_encoding', 'bool');
43 6
        $options = $resolver->resolve($config);
44
45 6
        $this->useContentEncoding = $options['use_content_encoding'];
46 6
    }
47
48
    /**
49
     * {@inheritdoc}
50
     */
51 4
    public function handleRequest(RequestInterface $request, callable $next, callable $first)
52
    {
53 4
        $encodings = extension_loaded('zlib') ? ['gzip', 'deflate'] : ['identity'];
54
55 4
        if ($this->useContentEncoding) {
56 3
            $request = $request->withHeader('Accept-Encoding', $encodings);
57
        }
58 4
        $encodings[] = 'chunked';
59 4
        $request = $request->withHeader('TE', $encodings);
60
61 4
        return $next($request)->then(function (ResponseInterface $response) {
62 4
            return $this->decodeResponse($response);
63 4
        });
64
    }
65
66
    /**
67
     * Decode a response body given its Transfer-Encoding or Content-Encoding value.
68
     *
69
     * @param ResponseInterface $response Response to decode
70
     *
71
     * @return ResponseInterface New response decoded
72
     */
73 4
    private function decodeResponse(ResponseInterface $response)
74
    {
75 4
        $response = $this->decodeOnEncodingHeader('Transfer-Encoding', $response);
76
77 4
        if ($this->useContentEncoding) {
78 3
            $response = $this->decodeOnEncodingHeader('Content-Encoding', $response);
79
        }
80
81 4
        return $response;
82
    }
83
84
    /**
85
     * Decode a response on a specific header (content encoding or transfer encoding mainly).
86
     *
87
     * @param string            $headerName Name of the header
88
     * @param ResponseInterface $response   Response
89
     *
90
     * @return ResponseInterface A new instance of the response decoded
91
     */
92 4
    private function decodeOnEncodingHeader($headerName, ResponseInterface $response)
93
    {
94 4
        if ($response->hasHeader($headerName)) {
95 3
            $encodings = $response->getHeader($headerName);
96 3
            $newEncodings = [];
97
98 3
            while ($encoding = array_pop($encodings)) {
99 3
                $stream = $this->decorateStream($encoding, $response->getBody());
100
101 3
                if (false === $stream) {
102
                    array_unshift($newEncodings, $encoding);
103
104
                    continue;
105
                }
106
107 3
                $response = $response->withBody($stream);
108
            }
109
110 3
            $response = $response->withHeader($headerName, $newEncodings);
111
        }
112
113 4
        return $response;
114
    }
115
116
    /**
117
     * Decorate a stream given an encoding.
118
     *
119
     * @param string          $encoding
120
     * @param StreamInterface $stream
121
     *
122
     * @return StreamInterface|false A new stream interface or false if encoding is not supported
123
     */
124 3
    private function decorateStream($encoding, StreamInterface $stream)
125
    {
126 3
        if ('chunked' === strtolower($encoding)) {
127 1
            return new Encoding\DechunkStream($stream);
128
        }
129
130 2
        if ('deflate' === strtolower($encoding)) {
131 1
            return new Encoding\DecompressStream($stream);
132
        }
133
134 1
        if ('gzip' === strtolower($encoding)) {
135 1
            return new Encoding\GzipDecodeStream($stream);
136
        }
137
138
        return false;
139
    }
140
}
141