Completed
Push — master ( 88a5ed...6cb890 )
by Joel
03:51
created

Client::configure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 17
ccs 12
cts 12
cp 1
rs 9.4286
cc 1
eloc 11
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Http\Client\Socket;
4
5
use Http\Client\Exception\NetworkException;
6
use Http\Client\HttpClient;
7
use Http\Message\MessageFactory;
8
use Psr\Http\Message\RequestInterface;
9
use Symfony\Component\OptionsResolver\Options;
10
use Symfony\Component\OptionsResolver\OptionsResolver;
11
12
/**
13
 * Socket Http Client
14
 *
15
 * Use stream and socket capabilities of the core of PHP to send HTTP requests
16
 *
17
 * @author Joel Wurtz <[email protected]>
18
 */
19
class Client implements HttpClient
20
{
21
    use RequestWriter;
22
    use ResponseReader;
23
24
    private $config = [
25
        'remote_socket'          => null,
26
        'timeout'                => null,
27
        'stream_context_options' => array(),
28
        'stream_context_param'   => array(),
29
        'ssl'                    => null,
30
        'write_buffer_size'      => 8192,
31
        'ssl_method'             => STREAM_CRYPTO_METHOD_TLS_CLIENT
32
    ];
33
34
    /**
35
     * @param MessageFactory $messageFactory
36
     * @param array          $config
37
     */
38 64
    public function __construct(MessageFactory $messageFactory, array $config = [])
39
    {
40 64
        $this->messageFactory = $messageFactory;
41 64
        $this->config = $this->configure($config);
42 64
    }
43
44
    /**
45
     * {@inheritdoc}
46
     */
47 64
    public function sendRequest(RequestInterface $request)
48
    {
49 64
        $remote  = $this->config['remote_socket'];
50 64
        $useSsl  = $this->config['ssl'];
51 64
        $request = $request->withHeader('Connection', 'close');
52
53 64
        if (null === $remote) {
54 58
            $remote = $this->determineRemoteFromRequest($request);
55 57
        }
56
57 63
        if (null === $useSsl) {
58 61
            $useSsl = ($request->getUri()->getScheme() == "https");
59 61
        }
60
61 63
        $socket = $this->createSocket($request, $remote, $useSsl);
62
63
        try {
64 59
            $this->writeRequest($socket, $request, $this->config['write_buffer_size']);
65 59
            $response = $this->readResponse($request, $socket);
66 59
        } catch (\Exception $e) {
67 1
            $this->closeSocket($socket);
68
69 1
            throw $e;
70
        }
71
72 58
        return $response;
73
    }
74
75
    /**
76
     * Create the socket to write request and read response on it
77
     *
78
     * @param RequestInterface $request Request for
79
     * @param string           $remote  Entrypoint for the connection
80
     * @param boolean          $useSsl  Whether to use ssl or not
81
     *
82
     * @throws NetworkException
83
     *
84
     * @return resource
85
     */
86 63
    protected function createSocket(RequestInterface $request, $remote, $useSsl)
87
    {
88 63
        $errNo  = null;
89 63
        $errMsg = null;
90 63
        $socket = @stream_socket_client($remote, $errNo, $errMsg, floor($this->config['timeout'] / 1000), STREAM_CLIENT_CONNECT, $this->config['stream_context']);
91
92 63
        if (false === $socket) {
93 3
            throw new NetworkException($errMsg, $request);
94
        }
95
96 60
        stream_set_timeout($socket, floor($this->config['timeout'] / 1000), $this->config['timeout'] % 1000);
97
98 60
        if ($useSsl) {
99 3
            if (false === @stream_socket_enable_crypto($socket, true, $this->config['ssl_method'])) {
100 1
                throw new NetworkException(sprintf('Cannot enable tls: %s', error_get_last()['message']), $request);
101
            }
102 2
        }
103
104 59
        return $socket;
105
    }
106
107
    /**
108
     * Close the socket, used when having an error
109
     *
110
     * @param resource $socket
111
     */
112 1
    protected function closeSocket($socket)
113
    {
114 1
        fclose($socket);
115 1
    }
116
117
    /**
118
     * Return configuration for the socket client
119
     *
120
     * @param array $config Configuration from user
121
     *
122
     * @return array Configuration resolved
123
     */
124 64
    protected function configure(array $config = [])
125
    {
126 64
        $resolver = new OptionsResolver();
127 64
        $resolver->setDefaults($this->config);
128 64
        $resolver->setDefault('stream_context', function (Options $options) {
129 64
            return stream_context_create($options['stream_context_options'], $options['stream_context_param']);
130 64
        });
131
132 64
        $resolver->setDefault('timeout', ini_get('default_socket_timeout') * 1000);
133
134 64
        $resolver->setAllowedTypes('stream_context_options', 'array');
135 64
        $resolver->setAllowedTypes('stream_context_param', 'array');
136 64
        $resolver->setAllowedTypes('stream_context', 'resource');
137 64
        $resolver->setAllowedTypes('ssl', ['bool', 'null']);
138
139 64
        return $resolver->resolve($config);
140
    }
141
142
    /**
143
     * Return remote socket from the request
144
     *
145
     * @param RequestInterface $request
146
     *
147
     * @throws NetworkException When no remote can be determined from the request
148
     *
149
     * @return string
150
     */
151 58
    private function determineRemoteFromRequest(RequestInterface $request)
152
    {
153 58
        if ($request->getUri()->getHost() == "" && !$request->hasHeader('Host')) {
154 1
            throw new NetworkException("Cannot find connection endpoint for this request", $request);
155
        }
156
157 57
        $host = $request->getUri()->getHost();
158 57
        $port = $request->getUri()->getPort() ?: ($request->getUri()->getScheme() == "https" ? 443 : 80);
159 57
        $endpoint = sprintf("%s:%s", $host, $port);
160
161
        // If use the host header if present for the endpoint
162 57
        if (empty($host) && $request->hasHeader('Host')) {
163 1
            $endpoint = $request->getHeaderLine('Host');
164 1
        }
165
166 57
        return sprintf('tcp://%s', $endpoint);
167
    }
168
}
169