Thursday, September 7, 2017

A Guide to Async HTTP Requests for WordPress

One thing that makes working with PHP simple is that it is serial — each line of code is executed in order. This is also a limitation when we need to build a program that interacts with multiple APIs or other services since HTTP requests take time and can block page rendering.

I discussed this problem in my article on combining data from multiple REST API endpoints using PHP. My best advice for avoiding this problem is using caching or JavaScript instead.

One great thing about JavaScript is that there are many simple ways to do asynchronous HTTP requests, such as jQuery AJAX's easy API.

While PHP may be serial, there are several ways to perform asynchronous HTTP requests in a PHP application. I've written about asynchronous tasks in WordPress before. That was great for use cases where it was OK to wait until after the current session for the task to run. Delicious Brains has an excellent article on other approaches to that method of async PHP in WordPress.

But, what if you need the results of multiple HTTP requests before completing the current session, then those strategies are not going to work. Instead, making multiple HTTP requests at once and then proceeding is a better approach. In this article, I'll look at how to do that with WordPress and provide some resources on going further with asynchronous PHP.

A Quick Look At Requests

In WordPress 4.5 or later, Requests for PHP library is used for HTTP requests. This is pretty transparent, but when you use functions like wp_remote_get() or wp_remote_post(), by default Requests is used to make the request.

As a result, we can safely use Requests on its own in WordPress. It's a useful and simple library that is maintained by Ryan McCue.

The main Requests class has a static method for each of the HTTP methods. So we can use Requests::get() for GET requests and Requests::post() for POST requests. Here are two quick examples. The first gets a page of posts from a remote site via the WordPress REST API. The second makes a POST request to create a post via the WordPress REST API on a remote site:

<?php //Get posts $post = Requests::get( 'https://yoursite/wp-json/wp/v2/posts' ); //Create post using basic auth $post = Requests::post( 'https://yoursite/wp-json/wp/v2/posts', [ 'auth' => new Requests_Auth_Basic( [ 'admin', 'password'] ) ], [ 'title' => 'Hi Roy', 'content' => 'hello...' ] );

1

2

3

4

5

6

7

8

9

10

11

12

13

14

<?php

//Get posts

$post =  Requests::get( 'https://yoursite/wp-json/wp/v2/posts' );

//Create post using basic auth

$post = Requests::post(

'https://yoursite/wp-json/wp/v2/posts',

[

'auth' => new Requests_Auth_Basic( [ 'admin', 'password'] )

],

[

'title' => 'Hi Roy',

'content' => 'hello...'

]

);

That's cool, but nothing we couldn't do with wp_remote_*() type functions. In fact, it's probably best to stick to those standards. But, the point of this article is to discuss asynchronous requests.

Getting Asynchronous

I haven't seen it used much yet, but WordPress has had the ability to do asynchronous PHP requests without extra dependencies since version 4.5. The Requests class has a request_multiple() method.

For example, to use the example that I started with — combining posts from multiple sites' REST APIs, we can combine two GET requests like this:

<?php $request_1 = [ 'url' => 'https://calderaforms.com/wp-json/wp/v2/posts', 'type' => 'GET' ]; $request_2 = [ 'url' => 'https://joshpress.net/wp-json/wp/v2/posts', 'type' => 'GET' ]; $responses = Requests::request_multiple([ $request_1, $request_2 ] ); /** @var Requests_Response $response */ foreach ( $responses as $response ){ $data = []; if( is_a( $response, 'Requests_Response' ) ){ $data[] = json_decode( $response->body ); } }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

<?php

$request_1 = [

'url' => 'https://calderaforms.com/wp-json/wp/v2/posts',

'type' => 'GET'

];

$request_2 = [

'url' => 'https://joshpress.net/wp-json/wp/v2/posts',

'type' => 'GET'

];

$responses = Requests::request_multiple([ $request_1, $request_2 ] );

/** @var Requests_Response $response */

foreach ( $responses as $response ){

$data = [];

if( is_a( $response, 'Requests_Response' ) ){

$data[] = json_decode( $response->body );

}

}

As you can see, we passed an array of request parameters, URL and method for each. The two requests are made concurrently and we can work with $responses once both are complete. Keep in mind that if by "complete" I mean successful or failed. That's why in the loop I'm checking if the returned value is an Requests_Response object. If would be a Requests_Exception object if the request failed.

Pretty simple right? There is a ton of complexity hidden inside of Requests. That's good. Abstracting away complexity is a good thing.

We can also make POST requests. This could be useful if you need to add data from a form to multiple services — databases, CRMs, mailing list providers etc. Here is an example where the same person is being subscribed to two different MailChimp lists:

<?php $headers = [ 'Accept: application/vnd.api+json', 'Content-Type: application/vnd.api+json', //replace with actual API key 'Authorization: apikey 123456789' ]; $subscriber = [ 'email_address' => 'hello@hiroy.club', 'status' => 'subscribed' ]; $request_1 = [ 'url' => 'https://us10.api.mailchimp.com/3.0/lists/42/members', 'type' => 'POST', 'headers' => $headers, 'data' => $subscriber ]; $request_2 = [ 'url' => 'https://us10.api.mailchimp.com/3.0/lists/4000000/members', 'type' => 'POST', 'headers' => $headers, 'data' => $subscriber ]; $responses = Requests::request_multiple([ $request_1, $request_2 ] ); /** @var Requests_Response $response */ foreach ( $responses as $response ){ $data = []; if( is_a( $response, 'Requests_Response' ) ){ $data[] = json_decode( $response->body ); } }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

<?php

$headers = [

'Accept: application/vnd.api+json',

'Content-Type: application/vnd.api+json',

//replace with actual API key

'Authorization: apikey 123456789'

];

$subscriber = [

'email_address' => 'hello@hiroy.club',

'status' => 'subscribed'

];

$request_1 = [

'url' => 'https://us10.api.mailchimp.com/3.0/lists/42/members',

'type' => 'POST',

'headers' => $headers,

'data' => $subscriber

];

$request_2 = [

'url' => 'https://us10.api.mailchimp.com/3.0/lists/4000000/members',

'type' => 'POST',

'headers' => $headers,

'data' => $subscriber

];

$responses = Requests::request_multiple([ $request_1, $request_2 ] );

/** @var Requests_Response $response */

foreach ( $responses as $response ){

$data = [];

if( is_a( $response, 'Requests_Response' ) ){

$data[] = json_decode( $response->body );

}

}

Other Options?

Requests is PHP 5.2 compatible and built into WordPress. So it's the most compatible and least likely to cause conflicts option. Guzzle has support for asynchronous HTTP requests  as well. If you have more complex needs, Guzzle is a great choice. This article was actually inspired by a recent problem I had caused by waiting on many serial HTTP requests. I solved that problem with Guzzle async requests, but then went looking for "The WordPress way."

If you want to get really deep into asynchronous PHP, the ReactPHP library (no relationship to ReactJS) can handle asynchronous HTTP and more. Facebook's PHP-fork Hack is built with asynchronous PHP in mind.

As the complexity of your application grows, and the more you integrate APIs — third-party or your own — into your application, the more of a problem blocking HTTP requests will be. I hope this introduction to asynchronous HTTP with WordPress will help.

Josh Pollock

Josh is a WordPress developer and educator. He is Founder/ Lead Developer/ Space Astronaut Grade 3 for Caldera Labs, makers of awesome WordPress tools including Caldera Forms — a drag and drop, responsive WordPress form builder. He teaches WordPress development at Caldera Learn.


Source: A Guide to Async HTTP Requests for WordPress

No comments:

Post a Comment