Completed
Pull Request — master (#6)
by Michael
01:49 queued 49s
created

Client::sanitizeWithTrace()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
namespace Http\Adapter\Zend;
4
5
use Http\Client\Exception\NetworkException;
6
use Http\Client\HttpClient;
7
use Http\Discovery\MessageFactoryDiscovery;
8
use Http\Message\ResponseFactory;
9
use Psr\Http\Message\RequestInterface;
10
use Psr\Http\Message\ResponseInterface;
11
use Zend\Http\Client as ZendClient;
12
use Zend\Http\Exception\RuntimeException;
13
use Zend\Http\Header\GenericHeader;
14
use Zend\Http\Headers;
15
use Zend\Http\Request;
16
17
class Client implements HttpClient
18
{
19
    /** @var ZendClient */
20
    private $client;
21
22
    /** @var ResponseFactory */
23
    private $responseFactory;
24
25 82
    public function __construct(ZendClient $client = null, ResponseFactory $responseFactory = null)
26
    {
27 82
        $this->client = $client ?: new ZendClient();
28 82
        $this->responseFactory = $responseFactory ?: MessageFactoryDiscovery::find();
29 82
    }
30
31
    /**
32
     * {@inheritdoc}
33
     */
34 82
    public function sendRequest(RequestInterface $request): ResponseInterface
35
    {
36 82
        $request = $this->sanitizeRequest($request);
37 82
        $headers = new Headers();
38
39 82
        foreach ($request->getHeaders() as $key => $value) {
40 82
            $headers->addHeader(new GenericHeader($key, $request->getHeaderLine($key)));
41
        }
42
43 82
        $zendRequest = new Request();
44 82
        $zendRequest->setMethod($request->getMethod());
45 82
        $zendRequest->setUri((string) $request->getUri());
46 82
        $zendRequest->setHeaders($headers);
47 82
        $zendRequest->setContent($request->getBody()->getContents());
48
49
        $options = [
50 82
            'httpversion' => $request->getProtocolVersion(),
51
        ];
52
53 82
        if (extension_loaded('curl')) {
54 82
            $options['curloptions'] = [
55 82
                CURLOPT_HTTP_VERSION => $this->getProtocolVersion($request->getProtocolVersion()),
56
            ];
57
        }
58
59 82
        $this->client->setOptions($options);
60
61 82
        if ($this->client->getAdapter() instanceof ZendClient\Adapter\Curl && $request->getMethod()) {
62 31
            $request = $request->withHeader('Content-Length', '0');
63
        }
64
65
        try {
66 82
            $zendResponse = $this->client->send($zendRequest);
67 2
        } catch (RuntimeException $exception) {
68 2
            throw new NetworkException($exception->getMessage(), $request, $exception);
69
        }
70
71 80
        return $this->responseFactory->createResponse(
72 80
            $zendResponse->getStatusCode(),
73 80
            $zendResponse->getReasonPhrase(),
74 80
            $zendResponse->getHeaders()->toArray(),
75 80
            $zendResponse->getContent(),
76 80
            $zendResponse->getVersion()
77
        );
78
    }
79
80 82
    private function sanitizeRequest(RequestInterface $request)
81
    {
82 82
        $request = $this->sanitizeWithTrace($request);
83 82
        $request = $this->sanitizeWithCurl($request);
84
85 82
        return $request;
86
    }
87
88
    /**
89
     * Zend request remove the body if it's a trace but does not rewrite the content length header,
90
     * This can lead to error from the server has it can expect a specific content length, but don't have
91
     * a body, so we set content-length to 0 to avoid bad reading from the server.
92
     *
93
     * @param RequestInterface $request
94
     *
95
     * @return RequestInterface
96
     */
97 82
    private function sanitizeWithTrace(RequestInterface $request)
98
    {
99 82
        if ('TRACE' === $request->getMethod()) {
100 4
            $request = $request->withHeader('Content-Length', '0');
101
        }
102
103 82
        return $request;
104
    }
105
106
    /**
107
     * On cUrl Adapter, zend does not include the body if it's not a POST, PUT or PATCH request but does not
108
     * rewrite the content length header.
109
     * This can lead to error from the server has it can expect a specific content length, but don't have
110
     * a body, so we set content-length to 0 to avoid bad reading from the server.
111
     *
112
     * @param RequestInterface $request
113
     *
114
     * @return RequestInterface
115
     */
116 82
    private function sanitizeWithCurl(RequestInterface $request)
117
    {
118 82
        if ($this->client->getAdapter() instanceof ZendClient\Adapter\Curl && !in_array($request->getMethod(), [
119 31
                'POST',
120
                'PUT',
121
                'PATCH',
122 82
            ], true)) {
123 23
            $request = $request->withHeader('Content-Length', '0');
124
        }
125
126 82
        return $request;
127
    }
128
129
    /**
130
     * Return cURL constant for specified HTTP version.
131
     *
132
     * @param string $requestVersion
133
     *
134
     * @throws \UnexpectedValueException if unsupported version requested
135
     *
136
     * @return int
137
     */
138 82
    private function getProtocolVersion($requestVersion)
139
    {
140
        switch ($requestVersion) {
141 82
            case '1.0':
142 18
                return CURL_HTTP_VERSION_1_0;
143 64
            case '1.1':
144 64
                return CURL_HTTP_VERSION_1_1;
145
            case '2.0':
146
                if (defined('CURL_HTTP_VERSION_2_0')) {
147
                    return CURL_HTTP_VERSION_2_0;
148
                }
149
150
                throw new \UnexpectedValueException('libcurl 7.33 needed for HTTP 2.0 support');
151
        }
152
153
        return CURL_HTTP_VERSION_NONE;
154
    }
155
}
156