HttpClient   A
last analyzed

Complexity

Total Complexity 10

Size/Duplication

Total Lines 106
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 93.18%

Importance

Changes 0
Metric Value
wmc 10
lcom 1
cbo 7
dl 0
loc 106
ccs 41
cts 44
cp 0.9318
rs 10
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
B sendRequest() 0 73 9
1
<?php
2
3
namespace Kodus\Http;
4
5
use Psr\Http\Client\ClientInterface;
6
use Psr\Http\Message\RequestInterface;
7
use Psr\Http\Message\ResponseFactoryInterface;
8
use Psr\Http\Message\ResponseInterface;
9
use Psr\Http\Message\StreamFactoryInterface;
10
use function array_shift;
11
use function fopen;
12
use function implode;
13
use function preg_match;
14
use function stream_context_create;
15
use function stream_filter_append;
16
use function stream_get_meta_data;
17
18
class HttpClient implements ClientInterface
19
{
20
    /**
21
     * @var ResponseFactoryInterface
22
     */
23
    private $response_factory;
24
25
    /**
26
     * @var string|null
27
     */
28
    private $proxy;
29
30
    /**
31
     * @var StreamFactoryInterface
32
     */
33
    private $stream_factory;
34
35
    /**
36
     * @param ResponseFactoryInterface $response_factory
37
     * @param StreamFactoryInterface   $stream_factory
38
     * @param string|null              $proxy optional proxy server for outgoing HTTP requests (e.g. "tcp://proxy.example.com:5100")
39
     */
40 1
    public function __construct(
41
        ResponseFactoryInterface $response_factory,
42
        StreamFactoryInterface $stream_factory,
43
        ?string $proxy = null
44
    ) {
45 1
        $this->proxy = $proxy;
46 1
        $this->response_factory = $response_factory;
47 1
        $this->stream_factory = $stream_factory;
48 1
    }
49
50 1
    public function sendRequest(RequestInterface $request): ResponseInterface
51
    {
52 1
        $headers = [];
53
54 1
        foreach ($request->getHeaders() as $name => $values) {
55 1
            foreach ($values as $value) {
56 1
                $headers[] = "{$name}: {$value}";
57
            }
58
        }
59
60 1
        $uri = $request->getUri()->__toString();
61
62 1
        $method = strtoupper($request->getMethod());
63
64 1
        $context = stream_context_create([
65
            "http" => [
66
                // http://docs.php.net/manual/en/context.http.php
67 1
                "method"          => $method,
68 1
                "header"          => implode("\r\n", $headers),
69 1
                "content"         => $request->getBody()->__toString(),
70
                "ignore_errors"   => true,
71 1
                "follow_location" => 0, // do not follow redirects
72 1
                "proxy"           => $this->proxy,
73
            ],
74
        ]);
75
76 1
        $stream = @fopen($uri, "r", false, $context);
77
78 1
        if ($stream === false) {
79
            throw new HttpClientException("unable to open resource: {$method} {$uri}");
80
        }
81
82 1
        $headers = stream_get_meta_data($stream)['wrapper_data'];
83
84 1
        $status_line = array_shift($headers);
85
86 1
        if (preg_match('{^HTTP\/(?<version>\S*)\s(?<code>\d{3})\s*(?<reasonPhrase>.*)}', $status_line, $match) !== 1) {
87
            throw new HttpClientException("invalid HTTP status line: {$status_line}");
88
        }
89
90 1
        $version = $match["version"];
91 1
        $code = (int) $match["code"];
92 1
        $reasonPhrase = $match["reasonPhrase"];
93
94 1
        $response = $this->response_factory
95 1
            ->createResponse($code, $reasonPhrase)
96 1
            ->withProtocolVersion($version);
97
98 1
        foreach ($headers as $header) {
99 1
            if (preg_match('{^(?<name>[^\:]+)\:(?<value>.*)}', $header, $match) !== 1) {
100
                throw new HttpClientException("malformed header value: {$header}");
101
            }
102
103 1
            $response = $response->withAddedHeader($match["name"], trim($match["value"]));
104
        }
105
106 1
        if ($response->hasHeader("Content-Encoding")) {
107 1
            $encoding = $response->getHeaderLine("Content-Encoding");
108
109 1
            switch ($encoding) {
110 1
                case "gzip":
111 1
                    stream_filter_append($stream, "zlib.inflate", STREAM_FILTER_READ, ["window" => 30]);
112 1
                    break;
113
114
                default:
115 1
                    throw new HttpClientException("unsupported Content-Encoding: {$encoding}");
116
            }
117
        }
118
119 1
        return $response->withBody(
120 1
            $this->stream_factory->createStreamFromResource($stream)
121
        );
122
    }
123
}
124