Skip to content

Commit 1c2a87b

Browse files
REST API: Improve performance for HEAD requests.
By default, the REST API responds to HEAD rqeuests by calling the GET handler and omitting the body from the response. While convenient, this ends up performing needless work that slows down the API response time. This commit adjusts the Core controllers to specifically handle HEAD requests by not preparing the response body. Fixes #56481. Props antonvlasenko, janusdev, ironprogrammer, swissspidy, spacedmonkey, mukesh27, mamaduka, timothyblynjacobs. git-svn-id: https://develop.svn.wordpress.org/trunk@59899 602fd350-edb4-49c9-b593-d223f7449a82
1 parent c19e4a4 commit 1c2a87b

File tree

43 files changed

+2677
-303
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2677
-303
lines changed

src/wp-includes/rest-api/class-wp-rest-request.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,18 @@ public function get_headers() {
161161
return $this->headers;
162162
}
163163

164+
/**
165+
* Determines if the request is the given method.
166+
*
167+
* @since 6.8.0
168+
*
169+
* @param string $method HTTP method.
170+
* @return bool Whether the request is of the given method.
171+
*/
172+
public function is_method( $method ) {
173+
return $this->get_method() === strtoupper( $method );
174+
}
175+
164176
/**
165177
* Canonicalizes the header name.
166178
*

src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,10 @@ public function get_items( $request ) {
305305
return $parent;
306306
}
307307

308+
if ( $request->is_method( 'HEAD' ) ) {
309+
// Return early as this handler doesn't add any response headers.
310+
return new WP_REST_Response();
311+
}
308312
$response = array();
309313
$parent_id = $parent->ID;
310314
$revisions = wp_get_post_revisions( $parent_id, array( 'check_enabled' => false ) );
@@ -448,6 +452,11 @@ public function prepare_item_for_response( $item, $request ) {
448452
// Restores the more descriptive, specific name for use within this method.
449453
$post = $item;
450454

455+
// Don't prepare the response body for HEAD requests.
456+
if ( $request->is_method( 'HEAD' ) ) {
457+
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php */
458+
return apply_filters( 'rest_prepare_autosave', new WP_REST_Response(), $post, $request );
459+
}
451460
$response = $this->revisions_controller->prepare_item_for_response( $post, $request );
452461
$fields = $this->get_fields_for_response( $request );
453462

src/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ public function get_items_permissions_check( $request ) {
8181
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
8282
*/
8383
public function get_items( $request ) {
84+
if ( $request->is_method( 'HEAD' ) ) {
85+
// Return early as this handler doesn't add any response headers.
86+
return new WP_REST_Response();
87+
}
88+
8489
$response = array();
8590
$categories = WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered();
8691
foreach ( $categories as $category ) {

src/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ public function get_items_permissions_check( $request ) {
131131
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
132132
*/
133133
public function get_items( $request ) {
134+
if ( $request->is_method( 'HEAD' ) ) {
135+
// Return early as this handler doesn't add any response headers.
136+
return new WP_REST_Response();
137+
}
138+
134139
$data = array();
135140
$block_types = $this->block_registry->get_all_registered();
136141

@@ -250,6 +255,12 @@ public function prepare_item_for_response( $item, $request ) {
250255
// Restores the more descriptive, specific name for use within this method.
251256
$block_type = $item;
252257

258+
// Don't prepare the response body for HEAD requests.
259+
if ( $request->is_method( 'HEAD' ) ) {
260+
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php */
261+
return apply_filters( 'rest_prepare_block_type', new WP_REST_Response(), $block_type, $request );
262+
}
263+
253264
$fields = $this->get_fields_for_response( $request );
254265
$data = array();
255266

src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,14 @@ public function get_items( $request ) {
262262
$prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 );
263263
}
264264

265+
$is_head_request = $request->is_method( 'HEAD' );
266+
if ( $is_head_request ) {
267+
// Force the 'fields' argument. For HEAD requests, only post IDs are required to calculate pagination.
268+
$prepared_args['fields'] = 'ids';
269+
// Disable priming comment meta for HEAD requests to improve performance.
270+
$prepared_args['update_comment_meta_cache'] = false;
271+
}
272+
265273
/**
266274
* Filters WP_Comment_Query arguments when querying comments via the REST API.
267275
*
@@ -277,15 +285,17 @@ public function get_items( $request ) {
277285
$query = new WP_Comment_Query();
278286
$query_result = $query->query( $prepared_args );
279287

280-
$comments = array();
288+
if ( ! $is_head_request ) {
289+
$comments = array();
281290

282-
foreach ( $query_result as $comment ) {
283-
if ( ! $this->check_read_permission( $comment, $request ) ) {
284-
continue;
285-
}
291+
foreach ( $query_result as $comment ) {
292+
if ( ! $this->check_read_permission( $comment, $request ) ) {
293+
continue;
294+
}
286295

287-
$data = $this->prepare_item_for_response( $comment, $request );
288-
$comments[] = $this->prepare_response_for_collection( $data );
296+
$data = $this->prepare_item_for_response( $comment, $request );
297+
$comments[] = $this->prepare_response_for_collection( $data );
298+
}
289299
}
290300

291301
$total_comments = (int) $query->found_comments;
@@ -303,7 +313,7 @@ public function get_items( $request ) {
303313
$max_pages = (int) ceil( $total_comments / $request['per_page'] );
304314
}
305315

306-
$response = rest_ensure_response( $comments );
316+
$response = $is_head_request ? new WP_REST_Response() : rest_ensure_response( $comments );
307317
$response->header( 'X-WP-Total', $total_comments );
308318
$response->header( 'X-WP-TotalPages', $max_pages );
309319

@@ -1041,6 +1051,12 @@ public function prepare_item_for_response( $item, $request ) {
10411051
// Restores the more descriptive, specific name for use within this method.
10421052
$comment = $item;
10431053

1054+
// Don't prepare the response body for HEAD requests.
1055+
if ( $request->is_method( 'HEAD' ) ) {
1056+
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php */
1057+
return apply_filters( 'rest_prepare_comment', new WP_REST_Response(), $comment, $request );
1058+
}
1059+
10441060
$fields = $this->get_fields_for_response( $request );
10451061
$data = array();
10461062

src/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ public function get_items( $request ) {
8989

9090
$collections_page = array_slice( $collections_all, ( $page - 1 ) * $per_page, $per_page );
9191

92+
$is_head_request = $request->is_method( 'HEAD' );
93+
9294
$items = array();
9395
foreach ( $collections_page as $collection ) {
9496
$item = $this->prepare_item_for_response( $collection, $request );
@@ -97,11 +99,21 @@ public function get_items( $request ) {
9799
if ( is_wp_error( $item ) ) {
98100
continue;
99101
}
102+
103+
/*
104+
* Skip preparing the response body for HEAD requests.
105+
* Cannot exit earlier due to backward compatibility reasons,
106+
* as validation occurs in the prepare_item_for_response method.
107+
*/
108+
if ( $is_head_request ) {
109+
continue;
110+
}
111+
100112
$item = $this->prepare_response_for_collection( $item );
101113
$items[] = $item;
102114
}
103115

104-
$response = rest_ensure_response( $items );
116+
$response = $is_head_request ? new WP_REST_Response() : rest_ensure_response( $items );
105117

106118
$response->header( 'X-WP-Total', (int) $total_items );
107119
$response->header( 'X-WP-TotalPages', $max_pages );
@@ -175,13 +187,31 @@ public function prepare_item_for_response( $item, $request ) {
175187
return $collection_data;
176188
}
177189

190+
/**
191+
* Don't prepare the response body for HEAD requests.
192+
* Can't exit at the beginning of the method due to the potential need to return a WP_Error object.
193+
*/
194+
if ( $request->is_method( 'HEAD' ) ) {
195+
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php */
196+
return apply_filters( 'rest_prepare_font_collection', new WP_REST_Response(), $item, $request );
197+
}
198+
178199
foreach ( $data_fields as $field ) {
179200
if ( rest_is_field_included( $field, $fields ) ) {
180201
$data[ $field ] = $collection_data[ $field ];
181202
}
182203
}
183204
}
184205

206+
/**
207+
* Don't prepare the response body for HEAD requests.
208+
* Can't exit at the beginning of the method due to the potential need to return a WP_Error object.
209+
*/
210+
if ( $request->is_method( 'HEAD' ) ) {
211+
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php */
212+
return apply_filters( 'rest_prepare_font_collection', new WP_REST_Response(), $item, $request );
213+
}
214+
185215
$response = rest_ensure_response( $data );
186216

187217
if ( rest_is_field_included( '_links', $fields ) ) {

src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ public function get_items( $request ) {
163163
return $global_styles_config;
164164
}
165165

166+
$is_head_request = $request->is_method( 'HEAD' );
167+
166168
if ( wp_revisions_enabled( $parent ) ) {
167169
$registered = $this->get_collection_params();
168170
$query_args = array(
@@ -186,6 +188,14 @@ public function get_items( $request ) {
186188
}
187189
}
188190

191+
if ( $is_head_request ) {
192+
// Force the 'fields' argument. For HEAD requests, only post IDs are required to calculate pagination.
193+
$query_args['fields'] = 'ids';
194+
// Disable priming post meta for HEAD requests to improve performance.
195+
$query_args['update_post_term_cache'] = false;
196+
$query_args['update_post_meta_cache'] = false;
197+
}
198+
189199
$revisions_query = new WP_Query();
190200
$revisions = $revisions_query->query( $query_args );
191201
$offset = isset( $query_args['offset'] ) ? (int) $query_args['offset'] : 0;
@@ -228,14 +238,18 @@ public function get_items( $request ) {
228238
$page = (int) $request['page'];
229239
}
230240

231-
$response = array();
241+
if ( ! $is_head_request ) {
242+
$response = array();
232243

233-
foreach ( $revisions as $revision ) {
234-
$data = $this->prepare_item_for_response( $revision, $request );
235-
$response[] = $this->prepare_response_for_collection( $data );
236-
}
244+
foreach ( $revisions as $revision ) {
245+
$data = $this->prepare_item_for_response( $revision, $request );
246+
$response[] = $this->prepare_response_for_collection( $data );
247+
}
237248

238-
$response = rest_ensure_response( $response );
249+
$response = rest_ensure_response( $response );
250+
} else {
251+
$response = new WP_REST_Response();
252+
}
239253

240254
$response->header( 'X-WP-Total', (int) $total_revisions );
241255
$response->header( 'X-WP-TotalPages', (int) $max_pages );
@@ -275,6 +289,11 @@ public function get_items( $request ) {
275289
* @return WP_REST_Response|WP_Error Response object.
276290
*/
277291
public function prepare_item_for_response( $post, $request ) {
292+
// Don't prepare the response body for HEAD requests.
293+
if ( $request->is_method( 'HEAD' ) ) {
294+
return new WP_REST_Response();
295+
}
296+
278297
$parent = $this->get_parent( $request['parent'] );
279298
$global_styles_config = $this->get_decoded_global_styles_json( $post->post_content );
280299

src/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,11 @@ public function get_items( $request ) {
161161
return $raw_patterns;
162162
}
163163

164+
if ( $request->is_method( 'HEAD' ) ) {
165+
// Return early as this handler doesn't add any response headers.
166+
return new WP_REST_Response();
167+
}
168+
164169
$response = array();
165170

166171
if ( $raw_patterns ) {

src/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ public function get_items_permissions_check( $request ) {
109109
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
110110
*/
111111
public function get_items( $request ) {
112+
if ( $request->is_method( 'HEAD' ) ) {
113+
// Return early as this handler doesn't add any response headers.
114+
return new WP_REST_Response();
115+
}
116+
112117
$data = array();
113118
$types = get_post_types( array( 'show_in_rest' => true ), 'objects' );
114119

@@ -178,6 +183,12 @@ public function prepare_item_for_response( $item, $request ) {
178183
// Restores the more descriptive, specific name for use within this method.
179184
$post_type = $item;
180185

186+
// Don't prepare the response body for HEAD requests.
187+
if ( $request->is_method( 'HEAD' ) ) {
188+
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php */
189+
return apply_filters( 'rest_prepare_post_type', new WP_REST_Response(), $post_type, $request );
190+
}
191+
181192
$taxonomies = wp_list_filter( get_object_taxonomies( $post_type->name, 'objects' ), array( 'show_in_rest' => true ) );
182193
$taxonomies = wp_list_pluck( $taxonomies, 'name' );
183194
$base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;

src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,15 @@ static function ( $format ) {
411411
// Force the post_type argument, since it's not a user input variable.
412412
$args['post_type'] = $this->post_type;
413413

414+
$is_head_request = $request->is_method( 'HEAD' );
415+
if ( $is_head_request ) {
416+
// Force the 'fields' argument. For HEAD requests, only post IDs are required to calculate pagination.
417+
$args['fields'] = 'ids';
418+
// Disable priming post meta for HEAD requests to improve performance.
419+
$args['update_post_term_cache'] = false;
420+
$args['update_post_meta_cache'] = false;
421+
}
422+
414423
/**
415424
* Filters WP_Query arguments when querying posts via the REST API.
416425
*
@@ -443,22 +452,24 @@ static function ( $format ) {
443452
add_filter( 'post_password_required', array( $this, 'check_password_required' ), 10, 2 );
444453
}
445454

446-
$posts = array();
447-
448-
update_post_author_caches( $query_result );
449-
update_post_parent_caches( $query_result );
455+
if ( ! $is_head_request ) {
456+
$posts = array();
450457

451-
if ( post_type_supports( $this->post_type, 'thumbnail' ) ) {
452-
update_post_thumbnail_cache( $posts_query );
453-
}
458+
update_post_author_caches( $query_result );
459+
update_post_parent_caches( $query_result );
454460

455-
foreach ( $query_result as $post ) {
456-
if ( ! $this->check_read_permission( $post ) ) {
457-
continue;
461+
if ( post_type_supports( $this->post_type, 'thumbnail' ) ) {
462+
update_post_thumbnail_cache( $posts_query );
458463
}
459464

460-
$data = $this->prepare_item_for_response( $post, $request );
461-
$posts[] = $this->prepare_response_for_collection( $data );
465+
foreach ( $query_result as $post ) {
466+
if ( ! $this->check_read_permission( $post ) ) {
467+
continue;
468+
}
469+
470+
$data = $this->prepare_item_for_response( $post, $request );
471+
$posts[] = $this->prepare_response_for_collection( $data );
472+
}
462473
}
463474

464475
// Reset filter.
@@ -488,7 +499,7 @@ static function ( $format ) {
488499
);
489500
}
490501

491-
$response = rest_ensure_response( $posts );
502+
$response = $is_head_request ? new WP_REST_Response() : rest_ensure_response( $posts );
492503

493504
$response->header( 'X-WP-Total', (int) $total_posts );
494505
$response->header( 'X-WP-TotalPages', (int) $max_pages );
@@ -1833,6 +1844,12 @@ public function prepare_item_for_response( $item, $request ) {
18331844

18341845
setup_postdata( $post );
18351846

1847+
// Don't prepare the response body for HEAD requests.
1848+
if ( $request->is_method( 'HEAD' ) ) {
1849+
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
1850+
return apply_filters( "rest_prepare_{$this->post_type}", new WP_REST_Response(), $post, $request );
1851+
}
1852+
18361853
$fields = $this->get_fields_for_response( $request );
18371854

18381855
// Base fields for every post.

0 commit comments

Comments
 (0)