Completed
Pull Request — master (#19)
by
unknown
11:21 queued 01:18
created

Client::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 9
ccs 7
cts 7
cp 1
rs 9.6666
cc 2
eloc 5
nc 2
nop 2
crap 2
1
<?php
2
3
namespace Http\Client\Socket;
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 Symfony\Component\OptionsResolver\Options;
11
use Symfony\Component\OptionsResolver\OptionsResolver;
12
13
/**
14
 * Socket Http Client.
15
 *
16
 * Use stream and socket capabilities of the core of PHP to send HTTP requests
17
 *
18
 * @author Joel Wurtz <[email protected]>
19
 */
20
class Client implements HttpClient
21
{
22
    use RequestWriter;
23
    use ResponseReader;
24
25
    private $config = [
26
        'remote_socket' => null,
27
        'timeout' => null,
28
        'stream_context_options' => [],
29
        'stream_context_param' => [],
30
        'ssl' => null,
31
        'write_buffer_size' => 8192,
32
        'ssl_method' => STREAM_CRYPTO_METHOD_TLS_CLIENT,
33
    ];
34
35
    /**
36
     * Constructor.
37
     *
38
     * @param ResponseFactory $responseFactory Response factory for creating response
39
     * @param array           $config          {
40
     *
41
     *    @var string $remote_socket          Remote entrypoint (can be a tcp or unix domain address)
42
     *    @var int    $timeout                Timeout before canceling request
43
     *    @var array  $stream_context_options Context options as defined in the PHP documentation
44
     *    @var array  $stream_context_param   Context params as defined in the PHP documentation
45
     *    @var bool   $ssl                    Use ssl, default to scheme from request, false if not present
46
     *    @var int    $write_buffer_size      Buffer when writing the request body, defaults to 8192
47
     *    @var int    $ssl_method             Crypto method for ssl/tls, see PHP doc, defaults to STREAM_CRYPTO_METHOD_TLS_CLIENT
48
     * }
49
     */
50 64
    public function __construct(ResponseFactory $responseFactory = null, array $config = [])
51
    {
52 64
        if (null === $responseFactory) {
53 53
            $responseFactory = MessageFactoryDiscovery::find();
54 53
        }
55
56 64
        $this->responseFactory = $responseFactory;
57 64
        $this->config = $this->configure($config);
58 64
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63 64
    public function sendRequest(RequestInterface $request)
64
    {
65 64
        $remote = $this->config['remote_socket'];
66 64
        $useSsl = $this->config['ssl'];
67
68 64
        if (!$request->hasHeader('Connection')) {
69 11
            $request = $request->withHeader('Connection', 'close');
70 11
        }
71
72 64
        if( !$request->hasHeader('Content-Length') && false != ($bodySize = $request->getBody()->getSize()) ) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
73 58
            //$request = $request->withHeader('Content-Length', $bodySize);
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
74 57
        }
75
76 63
        if (null === $remote) {
77 61
            $remote = $this->determineRemoteFromRequest($request);
78 61
        }
79
80 63
        if (null === $useSsl) {
81
            $useSsl = ($request->getUri()->getScheme() === 'https');
82
        }
83 59
84 59
        $socket = $this->createSocket($request, $remote, $useSsl);
85 59
86 1
        try {
87
            $this->writeRequest($socket, $request, $this->config['write_buffer_size']);
88 1
            $response = $this->readResponse($request, $socket);
89
        } catch (\Exception $e) {
90
            $this->closeSocket($socket);
91 58
92
            throw $e;
93
        }
94
95
        return $response;
96
    }
97
98
    /**
99
     * Create the socket to write request and read response on it.
100
     *
101
     * @param RequestInterface $request Request for
102
     * @param string           $remote  Entrypoint for the connection
103
     * @param bool             $useSsl  Whether to use ssl or not
104
     *
105 63
     * @throws NetworkException When the connection fail
106
     *
107 63
     * @return resource Socket resource
108 63
     */
109 63
    protected function createSocket(RequestInterface $request, $remote, $useSsl)
110
    {
111 63
        $errNo = null;
112 3
        $errMsg = null;
113
        $socket = @stream_socket_client($remote, $errNo, $errMsg, floor($this->config['timeout'] / 1000), STREAM_CLIENT_CONNECT, $this->config['stream_context']);
114
115 60
        if (false === $socket) {
116
            throw new NetworkException($errMsg, $request);
117 60
        }
118 3
119 1
        stream_set_timeout($socket, floor($this->config['timeout'] / 1000), $this->config['timeout'] % 1000);
120
121 2
        if ($useSsl) {
122
            if (false === @stream_socket_enable_crypto($socket, true, $this->config['ssl_method'])) {
123 59
                throw new NetworkException(sprintf('Cannot enable tls: %s', error_get_last()['message']), $request);
124
            }
125
        }
126
127
        return $socket;
128
    }
129
130
    /**
131 1
     * Close the socket, used when having an error.
132
     *
133 1
     * @param resource $socket
134 1
     */
135
    protected function closeSocket($socket)
136
    {
137
        fclose($socket);
138
    }
139
140
    /**
141
     * Return configuration for the socket client.
142
     *
143 64
     * @param array $config Configuration from user
144
     *
145 64
     * @return array Configuration resolved
146 64
     */
147 64
    protected function configure(array $config = [])
148 64
    {
149 64
        $resolver = new OptionsResolver();
150
        $resolver->setDefaults($this->config);
151 64
        $resolver->setDefault('stream_context', function (Options $options) {
152
            return stream_context_create($options['stream_context_options'], $options['stream_context_param']);
153 64
        });
154 64
155 64
        $resolver->setDefault('timeout', ini_get('default_socket_timeout') * 1000);
156 64
157
        $resolver->setAllowedTypes('stream_context_options', 'array');
158 64
        $resolver->setAllowedTypes('stream_context_param', 'array');
159
        $resolver->setAllowedTypes('stream_context', 'resource');
160
        $resolver->setAllowedTypes('ssl', ['bool', 'null']);
161
162
        return $resolver->resolve($config);
163
    }
164
165
    /**
166
     * Return remote socket from the request.
167
     *
168
     * @param RequestInterface $request
169
     *
170 58
     * @throws NetworkException When no remote can be determined from the request
171
     *
172 58
     * @return string
173 1
     */
174
    private function determineRemoteFromRequest(RequestInterface $request)
175
    {
176 57
        if ($request->getUri()->getHost() == '' && !$request->hasHeader('Host')) {
177 57
            throw new NetworkException('Cannot find connection endpoint for this request', $request);
178 57
        }
179
180
        $host = $request->getUri()->getHost();
181 57
        $port = $request->getUri()->getPort() ?: ($request->getUri()->getScheme() == 'https' ? 443 : 80);
182 1
        $endpoint = sprintf('%s:%s', $host, $port);
183 1
184
        // If use the host header if present for the endpoint
185 57
        if (empty($host) && $request->hasHeader('Host')) {
186
            $endpoint = $request->getHeaderLine('Host');
187
        }
188
189
        return sprintf('tcp://%s', $endpoint);
190
    }
191
}
192