Skip to content
This repository was archived by the owner on May 30, 2025. It is now read-only.

Commit 87a1f4a

Browse files
authored
[HttpClient] CurlClient (#16)
Adds a CurlClient class to benefit from `libcurl` when available: ```php $client = Client::create(); // $client is CurlClient when libcurl extension is available and SocketClient otherwise $client->fetch("https://w.org"); ``` ### Motivateion Curl is faster than `stream_socket_*` functions and supports more HTTP features (e.g. HTTP 2.0). ### Implementation Before this PR there was a single `Client` class that used `stream_socket_*` functions for managing HTTP connections. After this PR there's: * `Client` abstract class with `Client::create( $options )` method. * `SocketClient` – the previous `Client` class. * `CurlClient` – the new libcurl-based HTTP client. The event-based API remains the same. Both clients pass a similar test suite. ### Testing instructions Confirm the tests worked
1 parent 966caf7 commit 87a1f4a

30 files changed

+2081
-1370
lines changed

components/Blueprints/DataReference/DataReferenceResolver.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
1212
use WordPress\Git\GitFilesystem;
1313
use WordPress\Git\GitRepository;
1414
use WordPress\HttpClient\ByteStream\SeekableRequestReadStream;
15-
use WordPress\HttpClient\Client;
15+
use WordPress\HttpClient\Client\SocketClient;
1616

1717
use function WordPress\Filesystem\wp_join_unix_paths;
1818
use function WordPress\Filesystem\wp_unix_sys_get_temp_dir;
1919

2020
class DataReferenceResolver {
2121
/**
22-
* @var Client
22+
* @var SocketClient
2323
*/
2424
private $client;
2525
/**
@@ -47,7 +47,7 @@ class DataReferenceResolver {
4747
*/
4848
private $tmpRoot;
4949

50-
public function __construct( Client $client, ?string $tmpRoot = null ) {
50+
public function __construct( SocketClient $client, ?string $tmpRoot = null ) {
5151
$this->client = $client;
5252
$this->tmpRoot = $tmpRoot ?: wp_unix_sys_get_temp_dir();
5353
}

components/Blueprints/Runner.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
use WordPress\Filesystem\InMemoryFilesystem;
5252
use WordPress\Filesystem\LocalFilesystem;
5353
use WordPress\HttpClient\ByteStream\RequestReadStream;
54-
use WordPress\HttpClient\Client;
54+
use WordPress\HttpClient\Client\SocketClient;
5555
use WordPress\Zip\ZipFilesystem;
5656

5757
use function WordPress\Encoding\utf8_is_valid_byte_stream;
@@ -65,7 +65,7 @@ class Runner {
6565
private $configuration;
6666
// TODO: Rename httpClient
6767
/**
68-
* @var Client
68+
* @var SocketClient
6969
*/
7070
private $client;
7171
/**
@@ -113,7 +113,7 @@ public function __construct( RunnerConfiguration $configuration ) {
113113
$this->configuration = $configuration;
114114
$this->validateConfiguration( $configuration );
115115

116-
$this->client = new Client();
116+
$this->client = new SocketClient();
117117
$this->mainTracker = new Tracker();
118118

119119
// Set up progress logging
@@ -652,16 +652,16 @@ private function createStepObject( string $stepType, array $data ) {
652652
case 'importContent':
653653
/**
654654
* Flatten the content declaration from
655-
*
655+
*
656656
* "content": [
657657
* {
658658
* "type": "posts",
659659
* "source": [ "post1.html", "post2.html" ]
660660
* }
661661
* ]
662-
*
662+
*
663663
* into
664-
*
664+
*
665665
* "content": [
666666
* {
667667
* "type": "posts",

components/Blueprints/Runtime.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
namespace WordPress\Blueprints;
44

5-
use Exception;
65
use Psr\Log\LoggerInterface;
7-
use RuntimeException;
86
use Symfony\Component\Process\Process;
97
use WordPress\Blueprints\DataReference\DataReference;
108
use WordPress\Blueprints\DataReference\DataReferenceResolver;
@@ -14,7 +12,7 @@
1412
use WordPress\ByteStream\WriteStream\FileWriteStream;
1513
use WordPress\Filesystem\Filesystem;
1614
use WordPress\Filesystem\LocalFilesystem;
17-
use WordPress\HttpClient\Client;
15+
use WordPress\HttpClient\Client\SocketClient;
1816

1917
use function WordPress\Filesystem\pipe_stream;
2018
use function WordPress\Filesystem\wp_join_unix_paths;
@@ -49,7 +47,7 @@ class Runtime {
4947
*/
5048
private $assets;
5149
/**
52-
* @var Client
50+
* @var SocketClient
5351
*/
5452
private $client;
5553
/**
@@ -69,7 +67,7 @@ public function __construct(
6967
Filesystem $targetFs,
7068
RunnerConfiguration $configuration,
7169
DataReferenceResolver $assets,
72-
Client $client,
70+
SocketClient $client,
7371
array $blueprint,
7472
string $tempRoot,
7573
DataReference $wpCliReference
@@ -83,7 +81,7 @@ public function __construct(
8381
$this->wpCliReference = $wpCliReference;
8482
}
8583

86-
public function getHttpClient(): Client {
84+
public function getHttpClient(): SocketClient {
8785
return $this->client;
8886
}
8987

components/Blueprints/SiteResolver/NewSiteResolver.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22

33
namespace WordPress\Blueprints\SiteResolver;
44

5-
use Exception;
65
use WordPress\Blueprints\DataReference\DataReference;
76
use WordPress\Blueprints\DataReference\File;
87
use WordPress\Blueprints\Exception\BlueprintExecutionException;
98
use WordPress\Blueprints\Progress\Tracker;
109
use WordPress\Blueprints\Runtime;
1110
use WordPress\Blueprints\VersionStrings\VersionConstraint;
12-
use WordPress\HttpClient\Client;
11+
use WordPress\HttpClient\Client\SocketClient;
1312
use WordPress\Zip\ZipFilesystem;
1413

1514
use function WordPress\Filesystem\copy_between_filesystems;
@@ -144,7 +143,7 @@ static public function resolve( Runtime $runtime, Tracker $progress, ?VersionCon
144143
$progress->finish();
145144
}
146145

147-
static private function resolveWordPressZipUrl( Client $client, string $version_string ): string {
146+
static private function resolveWordPressZipUrl( SocketClient $client, string $version_string ): string {
148147
if ( $version_string === 'latest' ) {
149148
return 'https://wordpress.org/latest.zip';
150149
}

components/Blueprints/Steps/SetSiteLanguageStep.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use Exception;
66
use WordPress\Blueprints\Progress\Tracker;
77
use WordPress\Blueprints\Runtime;
8-
use WordPress\HttpClient\Client;
8+
use WordPress\HttpClient\Client\SocketClient;
99
use WordPress\HttpClient\Request;
1010
use WordPress\Zip\ZipFilesystem;
1111

@@ -226,7 +226,7 @@ function(\$theme) {
226226
*
227227
* @return string|false
228228
*/
229-
private function getWordPressTranslationUrl( Runtime $runtime, string $wpVersion, string $language, Client $client ) {
229+
private function getWordPressTranslationUrl( Runtime $runtime, string $wpVersion, string $language, SocketClient $client ) {
230230
try {
231231
$api_url = "https://api.wordpress.org/translations/core/1.0/?version={$wpVersion}";
232232
$translations_data = $client->fetch( $api_url )->json();

components/Blueprints/Tests/Unit/DataReference/DataReferenceResolverTest.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use Exception;
66
use PHPUnit\Framework\MockObject\MockObject;
77
use PHPUnit\Framework\TestCase;
8-
use RuntimeException;
98
use WordPress\Blueprints\DataReference\DataReference;
109
use WordPress\Blueprints\DataReference\DataReferenceResolver;
1110
use WordPress\Blueprints\DataReference\Directory;
@@ -19,10 +18,10 @@
1918
use WordPress\ByteStream\MemoryPipe;
2019
use WordPress\ByteStream\ReadStream\ByteReadStream;
2120
use WordPress\Filesystem\Filesystem;
22-
use WordPress\HttpClient\Client;
21+
use WordPress\HttpClient\Client\SocketClient;
2322

2423
class DataReferenceResolverTest extends TestCase {
25-
/** @var Client&MockObject */
24+
/** @var SocketClient&MockObject */
2625
protected $client;
2726
protected $resolver;
2827
/** @var Filesystem&MockObject */
@@ -31,7 +30,7 @@ class DataReferenceResolverTest extends TestCase {
3130

3231
protected function setUp(): void {
3332
// @TODO: Don't mock. Just test actual resolution.
34-
$this->client = new Client();
33+
$this->client = new SocketClient();
3534
$this->resolver = new DataReferenceResolver( $this->client );
3635
$this->executionContext = $this->createMock( Filesystem::class );
3736
$this->tracker = $this->createMock( Tracker::class );

components/DataLiberation/Importer/AttachmentDownloader.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use Exception;
66
use WordPress\Filesystem\Filesystem;
7-
use WordPress\HttpClient\Client;
7+
use WordPress\HttpClient\Client\SocketClient;
88
use WordPress\HttpClient\Request;
99

1010
use function WordPress\Filesystem\wp_join_unix_paths;
@@ -25,7 +25,7 @@ class AttachmentDownloader {
2525
private $progress = array();
2626

2727
public function __construct( $output_root, $options = array() ) {
28-
$this->client = new Client();
28+
$this->client = new SocketClient();
2929
$this->output_root = $output_root;
3030
$this->source_from_filesystem = $options['source_from_filesystem'] ?? null;
3131
}
@@ -181,7 +181,7 @@ public function poll() {
181181
*/
182182

183183
switch ( $event ) {
184-
case Client::EVENT_GOT_HEADERS:
184+
case SocketClient::EVENT_GOT_HEADERS:
185185
if ( ! $request->is_redirected() ) {
186186
if ( file_exists( $this->output_paths[ $original_request_id ] . '.partial' ) ) {
187187
unlink( $this->output_paths[ $original_request_id ] . '.partial' );
@@ -196,7 +196,7 @@ public function poll() {
196196
}
197197
}
198198
break;
199-
case Client::EVENT_BODY_CHUNK_AVAILABLE:
199+
case SocketClient::EVENT_BODY_CHUNK_AVAILABLE:
200200
$chunk = $this->client->get_response_body_chunk();
201201
if ( ! fwrite( $this->fps[ $original_request_id ], $chunk ) ) {
202202
// @TODO: Don't echo the error message. Attach it to the import session instead for the user to review later on.
@@ -205,10 +205,10 @@ public function poll() {
205205
}
206206
$this->progress[ $original_url ]['received'] += strlen( $chunk );
207207
break;
208-
case Client::EVENT_FAILED:
208+
case SocketClient::EVENT_FAILED:
209209
$this->on_failure( $original_url, $original_request_id, $request->error );
210210
break;
211-
case Client::EVENT_FINISHED:
211+
case SocketClient::EVENT_FINISHED:
212212
if ( ! $request->is_redirected() ) {
213213
// Only process if this was the last request in the chain.
214214
$is_success = (

components/Git/GitRemote.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@
1818
use WordPress\Git\Model\TreeEntry;
1919
use WordPress\Git\Protocol\GitProtocolEncoderPipe;
2020
use WordPress\Git\Protocol\Parser\GitProtocolDecoder;
21-
use WordPress\HttpClient\Client;
21+
use WordPress\HttpClient\Client\SocketClient;
2222
use WordPress\HttpClient\Request;
2323

2424

2525
class GitRemote {
2626
/**
27-
* @var Client
27+
* @var SocketClient
2828
*/
2929
private $http_client;
3030
/**
@@ -36,7 +36,7 @@ class GitRemote {
3636
public function __construct( GitRepository $repository, $remote_name, $options = array() ) {
3737
$this->remote_name = $remote_name;
3838
$this->repository = $repository;
39-
$this->http_client = $options['http_client'] ?? new Client(
39+
$this->http_client = $options['http_client'] ?? new SocketClient(
4040
array(
4141
'timeout_ms' => 300000,
4242
)

components/HttpClient/ByteStream/RequestReadStream.php

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use WordPress\ByteStream\ByteStreamException;
66
use WordPress\ByteStream\ReadStream\BaseByteReadStream;
7-
use WordPress\HttpClient\Client;
7+
use WordPress\HttpClient\Client\SocketClient;
88
use WordPress\HttpClient\HttpClientException;
99
use WordPress\HttpClient\Request;
1010
use WordPress\HttpClient\Response;
@@ -15,7 +15,7 @@
1515
class RequestReadStream extends BaseByteReadStream {
1616

1717
/**
18-
* @var Client
18+
* @var SocketClient
1919
*/
2020
private $client;
2121
/**
@@ -43,7 +43,7 @@ public function __construct( $request, $options = array() ) {
4343
if ( is_string( $request ) ) {
4444
$request = new Request( $request );
4545
}
46-
$this->client = $options['client'] ?? new Client();
46+
$this->client = $options['client'] ?? new SocketClient();
4747
$this->request = $request;
4848
if ( isset( $options['buffer_size'] ) ) {
4949
$this->buffer_size = $options['buffer_size'];
@@ -87,13 +87,13 @@ protected function internal_pull( $max_bytes = 8096 ): string {
8787
return $this->pull_until_event(
8888
array(
8989
'max_bytes' => $max_bytes,
90-
'event' => Client::EVENT_BODY_CHUNK_AVAILABLE,
90+
'event' => SocketClient::EVENT_BODY_CHUNK_AVAILABLE,
9191
)
9292
);
9393
}
9494

9595
private function pull_until_event( $options = array() ) {
96-
$stop_at_event = $options['event'] ?? Client::EVENT_BODY_CHUNK_AVAILABLE;
96+
$stop_at_event = $options['event'] ?? SocketClient::EVENT_BODY_CHUNK_AVAILABLE;
9797
$this->ensure_is_enqueued();
9898

9999
while ( $this->client->await_next_event(
@@ -113,7 +113,7 @@ private function pull_until_event( $options = array() ) {
113113
continue;
114114
}
115115
switch ( $this->client->get_event() ) {
116-
case Client::EVENT_GOT_HEADERS:
116+
case SocketClient::EVENT_GOT_HEADERS:
117117
$this->response = $response;
118118
$content_length = $response->get_header( 'Content-Length' );
119119
if ( null !== $content_length ) {
@@ -126,12 +126,12 @@ private function pull_until_event( $options = array() ) {
126126
*/
127127
$this->remote_file_length = (int) $content_length;
128128
}
129-
if ( $stop_at_event === Client::EVENT_GOT_HEADERS ) {
129+
if ( $stop_at_event === SocketClient::EVENT_GOT_HEADERS ) {
130130
return true;
131131
}
132132
break;
133-
case Client::EVENT_BODY_CHUNK_AVAILABLE:
134-
if ( $stop_at_event === Client::EVENT_BODY_CHUNK_AVAILABLE ) {
133+
case SocketClient::EVENT_BODY_CHUNK_AVAILABLE:
134+
if ( $stop_at_event === SocketClient::EVENT_BODY_CHUNK_AVAILABLE ) {
135135
$body_chunk = $this->client->get_response_body_chunk();
136136

137137
if ( $this->progress_tracker ) {
@@ -144,7 +144,7 @@ private function pull_until_event( $options = array() ) {
144144
return $body_chunk;
145145
}
146146
break;
147-
case Client::EVENT_FINISHED:
147+
case SocketClient::EVENT_FINISHED:
148148
/**
149149
* If the server did not provide a Content-Length header,
150150
* backfill the file length with the number of downloaded
@@ -155,7 +155,7 @@ private function pull_until_event( $options = array() ) {
155155
}
156156

157157
return '';
158-
case Client::EVENT_FAILED:
158+
case SocketClient::EVENT_FAILED:
159159
// TODO: Think through error handling. Errors are expected when working with
160160
// the network. Should we auto retry? Make it easy for the caller to retry?
161161
// Something else?
@@ -174,7 +174,7 @@ public function await_response() {
174174
if ( ! $this->response ) {
175175
$this->pull_until_event(
176176
array(
177-
'event' => Client::EVENT_GOT_HEADERS,
177+
'event' => SocketClient::EVENT_GOT_HEADERS,
178178
)
179179
);
180180
}
@@ -188,7 +188,7 @@ public function await_response() {
188188
protected function internal_reached_end_of_data(): bool {
189189
return (
190190
Request::STATE_FINISHED === $this->request->latest_redirect()->state &&
191-
! $this->client->has_pending_event( $this->request, Client::EVENT_BODY_CHUNK_AVAILABLE ) &&
191+
! $this->client->has_pending_event( $this->request, SocketClient::EVENT_BODY_CHUNK_AVAILABLE ) &&
192192
strlen( $this->buffer ) === $this->offset_in_current_buffer
193193
);
194194
}

0 commit comments

Comments
 (0)