Make WordPress Core


Ignore:
Timestamp:
03/02/2025 10:05:08 PM (3 months ago)
Author:
TimothyBlynJacobs
Message:

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.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/tests/rest-api/rest-post-types-controller.php

    r58452 r59899  
    4545    }
    4646
    47     public function test_get_items_invalid_permission_for_context() {
     47    /**
     48     * @dataProvider data_readable_http_methods
     49     * @ticket 56481
     50     *
     51     * @param string $method HTTP method to use.
     52     */
     53    public function test_get_items_invalid_permission_for_context( $method ) {
    4854        wp_set_current_user( 0 );
    49         $request = new WP_REST_Request( 'GET', '/wp/v2/types' );
     55        $request = new WP_REST_Request( $method, '/wp/v2/types' );
    5056        $request->set_param( 'context', 'edit' );
    5157        $response = rest_get_server()->dispatch( $request );
    5258        $this->assertErrorResponse( 'rest_cannot_view', $response, 401 );
     59    }
     60
     61    /**
     62     * Data provider intended to provide HTTP method names for testing GET and HEAD requests.
     63     *
     64     * @return array
     65     */
     66    public static function data_readable_http_methods() {
     67        return array(
     68            'GET request'  => array( 'GET' ),
     69            'HEAD request' => array( 'HEAD' ),
     70        );
    5371    }
    5472
     
    5977        $data = $response->get_data();
    6078        $this->assertSame( array( 'category', 'post_tag' ), $data['taxonomies'] );
     79    }
     80
     81    /**
     82     * @dataProvider data_readable_http_methods
     83     * @ticket 56481
     84     *
     85     * @param string $method The HTTP method to use.
     86     */
     87    public function test_get_item_should_allow_adding_headers_via_filter( $method ) {
     88        $request = new WP_REST_Request( $method, '/wp/v2/types/post' );
     89
     90        $hook_name = 'rest_prepare_post_type';
     91        $filter    = new MockAction();
     92        $callback  = array( $filter, 'filter' );
     93        add_filter( $hook_name, $callback );
     94        $header_filter = new class() {
     95            public static function add_custom_header( $response ) {
     96                $response->header( 'X-Test-Header', 'Test' );
     97
     98                return $response;
     99            }
     100        };
     101        add_filter( $hook_name, array( $header_filter, 'add_custom_header' ) );
     102        $response = rest_get_server()->dispatch( $request );
     103        remove_filter( $hook_name, $callback );
     104        remove_filter( $hook_name, array( $header_filter, 'add_custom_header' ) );
     105
     106        $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' );
     107        $this->assertSame( 1, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' );
     108        $headers = $response->get_headers();
     109        $this->assertArrayHasKey( 'X-Test-Header', $headers, 'The "X-Test-Header" header should be present in the response.' );
     110        $this->assertSame( 'Test', $headers['X-Test-Header'], 'The "X-Test-Header" header value should be equal to "Test".' );
     111        if ( 'HEAD' !== $method ) {
     112            return null;
     113        }
     114        $this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
    61115    }
    62116
     
    107161    }
    108162
    109     public function test_get_item_invalid_type() {
    110         $request  = new WP_REST_Request( 'GET', '/wp/v2/types/invalid' );
     163    /**
     164     * @dataProvider data_readable_http_methods
     165     * @ticket 56481
     166     *
     167     * @param string $method HTTP method to use.
     168     */
     169    public function test_get_item_invalid_type( $method ) {
     170        $request  = new WP_REST_Request( $method, '/wp/v2/types/invalid' );
    111171        $response = rest_get_server()->dispatch( $request );
    112172        $this->assertErrorResponse( 'rest_type_invalid', $response, 404 );
     
    122182    }
    123183
    124     public function test_get_item_invalid_permission_for_context() {
     184    /**
     185     * @dataProvider data_readable_http_methods
     186     * @ticket 56481
     187     *
     188     * @param string $method HTTP method to use.
     189     */
     190    public function test_get_item_invalid_permission_for_context( $method ) {
    125191        wp_set_current_user( 0 );
    126         $request = new WP_REST_Request( 'GET', '/wp/v2/types/post' );
     192        $request = new WP_REST_Request( $method, '/wp/v2/types/post' );
    127193        $request->set_param( 'context', 'edit' );
    128194        $response = rest_get_server()->dispatch( $request );
     
    244310    public function additional_field_get_callback( $response_data ) {
    245311        return 123;
     312    }
     313
     314    /**
     315     * @ticket 56481
     316     */
     317    public function test_get_items_with_head_request_should_not_prepare_post_types_data() {
     318        $request   = new WP_REST_Request( 'HEAD', '/wp/v2/types' );
     319        $hook_name = 'rest_prepare_post_type';
     320        $filter    = new MockAction();
     321        $callback  = array( $filter, 'filter' );
     322        add_filter( $hook_name, $callback );
     323        $response = rest_get_server()->dispatch( $request );
     324        remove_filter( $hook_name, $callback );
     325        $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' );
     326        $this->assertSame( 0, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' );
     327        $this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
    246328    }
    247329
Note: See TracChangeset for help on using the changeset viewer.