SocketRequest   B
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 289
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 96.35%

Importance

Changes 0
Metric Value
wmc 37
lcom 1
cbo 1
dl 0
loc 289
ccs 132
cts 137
cp 0.9635
rs 8.6
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
B ping() 0 35 5
A setOption() 0 3 1
A getOption() 0 3 2
A getLastTransferInfo() 0 3 1
A getLastError() 0 3 1
A getLastErrorCode() 0 3 1
B execute() 0 34 1
A getResourceFromSocketClient() 0 16 1
B doMakeSocketRequest() 0 36 4
C getUrlComponents() 0 27 8
B postResponseToCallback() 0 12 6
B tryToFindFollowLocation() 0 23 5
1
<?php
2
3
namespace Onoi\HttpRequest;
4
5
use InvalidArgumentException;
6
use Closure;
7
8
/**
9
 * This class creates a remote socked connection to initiate an asynchronous
10
 * http/https request.
11
 *
12
 * Once a connection is established and content has been posted, the request
13
 * will close the connection. The receiving client is responsible for open
14
 * a separate process and initiated an independent transaction.
15
 *
16
 * @license GNU GPL v2+
17
 * @since 1.1
18
 *
19
 * @author mwjames
20
 */
21
class SocketRequest implements HttpRequest {
22
23
	/**
24
	 * @var array
25
	 */
26
	private $options = array();
27
28
	/**
29
	 * @var integer
30
	 */
31
	private $errno = 0;
32
33
	/**
34
	 * @var string
35
	 */
36
	private $errstr = '';
37
38
	/**
39
	 * @var string
40
	 */
41
	private $lastTransferInfo = '';
42
43
	/**
44
	 * @var boolean
45
	 */
46
	private $followedLocation = false;
47
48
	/**
49
	 * @since 1.1
50
	 *
51
	 * @param string|null $url
52
	 */
53 12
	public function __construct( $url = null ) {
54 12
		$this->setOption( ONOI_HTTP_REQUEST_URL, $url );
55 12
		$this->setOption( ONOI_HTTP_REQUEST_CONNECTION_TIMEOUT, 15 );
56 12
		$this->setOption( ONOI_HTTP_REQUEST_CONNECTION_FAILURE_REPEAT, 2 );
57 12
		$this->setOption( ONOI_HTTP_REQUEST_SOCKET_CLIENT_FLAGS, STREAM_CLIENT_ASYNC_CONNECT | STREAM_CLIENT_CONNECT );
58 12
		$this->setOption( ONOI_HTTP_REQUEST_METHOD, 'POST' );
59 12
		$this->setOption( ONOI_HTTP_REQUEST_CONTENT_TYPE, "application/x-www-form-urlencoded" );
60 12
		$this->setOption( ONOI_HTTP_REQUEST_SSL_VERIFYPEER, false );
61 12
		$this->setOption( ONOI_HTTP_REQUEST_FOLLOWLOCATION, true );
62 12
	}
63
64
	/**
65
	 * @since 1.1
66
	 *
67
	 * {@inheritDoc}
68
	 */
69 7
	public function ping() {
70
71 7
		if ( $this->getOption( ONOI_HTTP_REQUEST_URL ) === null ) {
72 2
			return false;
73
		}
74
75 6
		$urlComponents = $this->getUrlComponents( $this->getOption( ONOI_HTTP_REQUEST_URL ) );
76
77 6
		$resource = $this->getResourceFromSocketClient(
78 6
			$urlComponents,
79
			STREAM_CLIENT_CONNECT
80 6
		);
81
82 6
		if ( !$resource ) {
83 1
			return false;
84
		}
85
86 5
		stream_set_timeout( $resource, $this->getOption( ONOI_HTTP_REQUEST_CONNECTION_TIMEOUT ) );
87
88
		$httpMessage = (
89 5
			"HEAD "  . $urlComponents['path'] . " HTTP/1.1\r\n" .
90 5
			"Host: " . $urlComponents['host'] . "\r\n" .
91
			"Connection: Close\r\n\r\n"
92 5
		);
93
94 5
		$res = @fwrite( $resource, $httpMessage );
95
96 5
		if ( $this->getOption( ONOI_HTTP_REQUEST_FOLLOWLOCATION ) && $res !== false ) {
97 5
			$this->setOption( ONOI_HTTP_REQUEST_URL, $this->tryToFindFollowLocation( $resource, $urlComponents ) );
98 5
		}
99
100 5
		@fclose( $resource );
101
102 5
		return $res !== false;
103
	}
104
105
	/**
106
	 * @since 1.1
107
	 *
108
	 * {@inheritDoc}
109
	 */
110 17
	public function setOption( $name, $value ) {
111 17
		$this->options[$name] = $value;
112 17
	}
113
114
	/**
115
	 * @since 1.1
116
	 *
117
	 * {@inheritDoc}
118
	 */
119 13
	public function getOption( $name ) {
120 13
		return isset( $this->options[$name] ) ? $this->options[$name] : null;
121
	}
122
123
	/**
124
	 * @since 1.1
125
	 *
126
	 * {@inheritDoc}
127
	 */
128 1
	public function getLastTransferInfo( $name = null ) {
129 1
		return $this->lastTransferInfo;
130
	}
131
132
	/**
133
	 * @since 1.1
134
	 *
135
	 * {@inheritDoc}
136
	 */
137 1
	public function getLastError() {
138 1
		return $this->errstr;
139
	}
140
141
	/**
142
	 * @since 1.1
143
	 *
144
	 * {@inheritDoc}
145
	 */
146 1
	public function getLastErrorCode() {
147 1
		return $this->errno;
148
	}
149
150
	/**
151
	 * @since 1.1
152
	 *
153
	 * {@inheritDoc}
154
	 */
155 8
	public function execute() {
156
157 8
		$urlComponents = $this->getUrlComponents( $this->getOption( ONOI_HTTP_REQUEST_URL ) );
158
159 8
		$resource = $this->getResourceFromSocketClient(
160 8
			$urlComponents,
161 8
			$this->getOption( ONOI_HTTP_REQUEST_SOCKET_CLIENT_FLAGS )
162 8
		);
163
164
		// Defaults
165 8
		$requestResponse = new RequestResponse( array(
166 8
			'host' => $urlComponents['host'],
167 8
			'port' => $urlComponents['port'],
168 8
			'path' => $urlComponents['path'],
169 8
			'responseMessage'  => "$this->errstr ($this->errno)",
170 8
			'followedLocation' => false,
171 8
			'wasCompleted' => false,
172 8
			'wasAccepted'  => false,
173 8
			'connectionFailure' => -1,
174 8
			'requestProcTime'   => microtime( true )
175 8
		) );
176
177 8
		$this->doMakeSocketRequest(
178 8
			$urlComponents,
179 8
			$resource,
180
			$requestResponse
181 8
		);
182
183 8
		$this->postResponseToCallback(
184
			$requestResponse
185 8
		);
186
187 8
		return $requestResponse->get( 'wasCompleted' );
188
	}
189
190 8
	protected function getResourceFromSocketClient( $urlComponents, $flags ) {
191
192 8
		$context = stream_context_create();
193 8
		stream_context_set_option( $context, 'ssl', 'verify_peer', $this->getOption( ONOI_HTTP_REQUEST_SSL_VERIFYPEER ) );
194
195 8
		$resource = @stream_socket_client(
196 8
			$urlComponents['host'] . ':'. $urlComponents['port'],
197 8
			$this->errno,
198 8
			$this->errstr,
199 8
			$this->getOption( ONOI_HTTP_REQUEST_CONNECTION_TIMEOUT ),
200 8
			$flags,
201
			$context
202 8
		);
203
204 8
		return $resource;
205
	}
206
207 8
	private function doMakeSocketRequest( $urlComponents, $resource, RequestResponse &$requestResponse ) {
208
209 8
		if ( !$resource ) {
210 6
			return;
211
		}
212
213 2
		$requestCompleted = false;
214 2
		stream_set_timeout( $resource, $this->getOption( ONOI_HTTP_REQUEST_CONNECTION_TIMEOUT ) );
215
216
		$httpMessage = (
217 2
			strtoupper( $this->getOption( ONOI_HTTP_REQUEST_METHOD ) ) . " " . $urlComponents['path'] . " HTTP/1.1\r\n" .
218 2
			"Host: " . $urlComponents['host'] . "\r\n" .
219 2
			"Content-Type: " . $this->getOption( ONOI_HTTP_REQUEST_CONTENT_TYPE ) . "\r\n" .
220 2
			"Content-Length: " . strlen( $this->getOption( ONOI_HTTP_REQUEST_CONTENT ) ) . "\r\n" .
221 2
			"Connection: Close\r\n\r\n" .
222 2
			$this->getOption( ONOI_HTTP_REQUEST_CONTENT )
223 2
		);
224
225
		// Sometimes a response can fail (busy server, timeout etc.), try as for
226
		// as many times the FAILURE_REPEAT option dictates
227 2
		for ( $repeats = 0; $repeats < $this->getOption( ONOI_HTTP_REQUEST_CONNECTION_FAILURE_REPEAT ); $repeats++ ) {
228 2
			if ( $requestCompleted = @fwrite( $resource, $httpMessage ) ) {
229
				break;
230
			}
231 2
		}
232
233
		// Fetch the acknowledge response
234 2
		$this->lastTransferInfo = @fgets( $resource );
235 2
		@fclose( $resource );
236
237 2
		$requestResponse->set( 'responseMessage', $this->lastTransferInfo );
238 2
		$requestResponse->set( 'followedLocation', $this->followedLocation );
239 2
		$requestResponse->set( 'wasCompleted', (bool)$requestCompleted );
240 2
		$requestResponse->set( 'wasAccepted', (bool)preg_match( '#^HTTP/\d\.\d 202 #', $this->lastTransferInfo ) );
241 2
		$requestResponse->set( 'connectionFailure', $repeats );
242 2
	}
243
244 13
	private function getUrlComponents( $url ) {
245
246 13
		$urlComponents = parse_url( $url );
247 13
		$port = 80;
248
249 13
		$urlComponents['scheme'] = isset( $urlComponents['scheme'] ) ? $urlComponents['scheme'] : '';
250 13
		$urlComponents['host'] = isset( $urlComponents['host'] ) ? $urlComponents['host'] : '';
251 13
		$urlComponents['path'] = isset( $urlComponents['path'] ) ? $urlComponents['path'] : '';
252
253 13
		if ( isset( $urlComponents['scheme'] ) && $urlComponents['scheme'] == 'https' ) {
254 2
			$urlComponents['host'] = "tls://" . $urlComponents['host'];
255 2
			$port = 443;
256 2
		}
257
258 13
		if ( $this->getOption( ONOI_HTTP_REQUEST_SOCKET_PORT ) !== null ) {
259
			$port = $this->getOption( ONOI_HTTP_REQUEST_SOCKET_PORT );
260
		}
261
262 13
		$urlComponents['port'] = isset( $urlComponents['port'] ) ? $urlComponents['port'] : $port;
263
264
		return array(
265 13
			'scheme' => $urlComponents['scheme'],
266 13
			'host'   => $urlComponents['host'],
267 13
			'port'   => $urlComponents['port'],
268 13
			'path'   => $urlComponents['path']
269 13
		);
270
	}
271
272 8
	private function postResponseToCallback( RequestResponse $requestResponse ) {
273
274 8
		$requestResponse->set( 'requestProcTime', microtime( true ) - $requestResponse->get( 'requestProcTime' ) );
275
276 8
		if ( is_callable( $this->getOption( ONOI_HTTP_REQUEST_ON_COMPLETED_CALLBACK ) ) && $requestResponse->get( 'wasCompleted' ) ) {
277
			call_user_func_array( $this->getOption( ONOI_HTTP_REQUEST_ON_COMPLETED_CALLBACK ), array( $requestResponse ) );
278
		}
279
280 8
		if ( is_callable( $this->getOption( ONOI_HTTP_REQUEST_ON_FAILED_CALLBACK ) ) && ( !$requestResponse->get( 'wasCompleted' ) || !$requestResponse->get( 'wasAccepted' ) ) ) {
281 4
			call_user_func_array( $this->getOption( ONOI_HTTP_REQUEST_ON_FAILED_CALLBACK ), array( $requestResponse ) );
282 4
		}
283 8
	}
284
285 5
	private function tryToFindFollowLocation( $resource, $urlComponents ) {
286
287
		// http://stackoverflow.com/questions/3799134/how-to-get-final-url-after-following-http-redirections-in-pure-php
288
289 5
		$response = '';
290 5
		$host = str_replace( 'tls://', '', $urlComponents['host'] );
291
292 5
		while( !feof( $resource ) ) $response .= fread( $resource, 8192 );
293
294
		// Only try to match a 301 message (Moved Permanently)
295 5
		if ( preg_match( '#^HTTP/\d\.\d 301 #', $response ) && preg_match('/^Location: (.+?)$/m', $response, $matches ) ) {
296 4
			$this->followedLocation = true;
297
298 4
			if ( substr( $matches[1], 0, 1 ) == "/" ) {
299 3
				return $urlComponents['scheme'] . "://" . $host . trim( $matches[1] );
300
			}
301
302 1
			return trim( $matches[1] );
303
		}
304
305
		// Return the URL we know
306 1
		return $this->getOption( ONOI_HTTP_REQUEST_URL );
307
	}
308
309
}
310