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-taxonomies-controller.php

    r56746 r59899  
    6161    }
    6262
     63    /**
     64     * @ticket 56481
     65     */
     66    public function test_get_items_with_head_request_should_not_prepare_taxonomy_data() {
     67        $request   = new WP_REST_Request( 'HEAD', '/wp/v2/taxonomies' );
     68        $hook_name = 'rest_prepare_taxonomy';
     69        $filter    = new MockAction();
     70        $callback  = array( $filter, 'filter' );
     71        add_filter( $hook_name, $callback );
     72        $response = rest_get_server()->dispatch( $request );
     73        remove_filter( $hook_name, $callback );
     74        $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' );
     75        $this->assertSame( 0, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' );
     76        $this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
     77    }
     78
    6379    public function test_get_items_context_edit() {
    6480        wp_set_current_user( self::$contributor_id );
     
    8096    }
    8197
    82     public function test_get_items_invalid_permission_for_context() {
     98
     99    /**
     100     * @dataProvider data_readable_http_methods
     101     * @ticket 56481
     102     *
     103     * @param string $method HTTP method to use.
     104     */
     105    public function test_get_items_invalid_permission_for_context( $method ) {
    83106        wp_set_current_user( 0 );
    84         $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' );
     107        $request = new WP_REST_Request( $method, '/wp/v2/taxonomies' );
    85108        $request->set_param( 'context', 'edit' );
    86109        $response = rest_get_server()->dispatch( $request );
    87110        $this->assertErrorResponse( 'rest_cannot_view', $response, 401 );
     111    }
     112
     113    /**
     114     * Data provider intended to provide HTTP method names for testing GET and HEAD requests.
     115     *
     116     * @return array
     117     */
     118    public static function data_readable_http_methods() {
     119        return array(
     120            'GET request'  => array( 'GET' ),
     121            'HEAD request' => array( 'HEAD' ),
     122        );
    88123    }
    89124
     
    95130    }
    96131
    97     public function test_get_taxonomies_for_invalid_type() {
    98         $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' );
     132    /**
     133     * @dataProvider data_readable_http_methods
     134     * @ticket 56481
     135     *
     136     * @param string $method HTTP method to use.
     137     */
     138    public function test_get_taxonomies_for_invalid_type( $method ) {
     139        $request = new WP_REST_Request( $method, '/wp/v2/taxonomies' );
    99140        $request->set_param( 'type', 'wingding' );
    100141        $response = rest_get_server()->dispatch( $request );
    101142        $this->assertSame( 200, $response->get_status() );
     143        if ( 'HEAD' === $method ) {
     144            return null;
     145        }
    102146        $data = $response->get_data();
    103147        $this->assertSame( '{}', json_encode( $data ) );
     
    108152        $response = rest_get_server()->dispatch( $request );
    109153        $this->check_taxonomy_object_response( 'view', $response );
     154    }
     155
     156    /**
     157     * @dataProvider data_readable_http_methods
     158     * @ticket 56481
     159     *
     160     * @param string $method The HTTP method to use.
     161     */
     162    public function test_get_item_should_allow_adding_headers_via_filter( $method ) {
     163        $request   = new WP_REST_Request( 'HEAD', '/wp/v2/taxonomies/category' );
     164        $hook_name = 'rest_prepare_taxonomy';
     165        $filter    = new MockAction();
     166        $callback  = array( $filter, 'filter' );
     167        add_filter( $hook_name, $callback );
     168        $header_filter = new class() {
     169            public static function add_custom_header( $response ) {
     170                $response->header( 'X-Test-Header', 'Test' );
     171
     172                return $response;
     173            }
     174        };
     175        add_filter( $hook_name, array( $header_filter, 'add_custom_header' ) );
     176        $response = rest_get_server()->dispatch( $request );
     177        remove_filter( $hook_name, $callback );
     178        remove_filter( $hook_name, array( $header_filter, 'add_custom_header' ) );
     179
     180        $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' );
     181        $this->assertSame( 1, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' );
     182        $headers = $response->get_headers();
     183        $this->assertArrayHasKey( 'X-Test-Header', $headers, 'The "X-Test-Header" header should be present in the response.' );
     184        $this->assertSame( 'Test', $headers['X-Test-Header'], 'The "X-Test-Header" header value should be equal to "Test".' );
     185        if ( 'HEAD' !== $method ) {
     186            return null;
     187        }
     188        $this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
    110189    }
    111190
     
    119198    }
    120199
    121     public function test_get_item_invalid_permission_for_context() {
     200    /**
     201     * @dataProvider data_readable_http_methods
     202     * @ticket 56481
     203     *
     204     * @param string $method HTTP method to use.
     205     */
     206    public function test_get_item_invalid_permission_for_context( $method ) {
    122207        wp_set_current_user( 0 );
    123         $request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/category' );
     208        $request = new WP_REST_Request( $method, '/wp/v2/taxonomies/category' );
    124209        $request->set_param( 'context', 'edit' );
    125210        $response = rest_get_server()->dispatch( $request );
     
    127212    }
    128213
    129     public function test_get_invalid_taxonomy() {
    130         $request  = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/invalid' );
     214    /**
     215     * @dataProvider data_readable_http_methods
     216     * @ticket 56481
     217     *
     218     * @param string $method HTTP method to use.
     219     */
     220    public function test_get_invalid_taxonomy( $method ) {
     221        $request  = new WP_REST_Request( $method, '/wp/v2/taxonomies/invalid' );
    131222        $response = rest_get_server()->dispatch( $request );
    132223        $this->assertErrorResponse( 'rest_taxonomy_invalid', $response, 404 );
    133224    }
    134225
    135     public function test_get_non_public_taxonomy_not_authenticated() {
     226    /**
     227     * @dataProvider data_readable_http_methods
     228     * @ticket 56481
     229     *
     230     * @param string $method HTTP method to use.
     231     */
     232    public function test_get_non_public_taxonomy_not_authenticated( $method ) {
    136233        register_taxonomy( 'api-private', 'post', array( 'public' => false ) );
    137234
    138         $request  = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/api-private' );
     235        $request  = new WP_REST_Request( $method, '/wp/v2/taxonomies/api-private' );
    139236        $response = rest_get_server()->dispatch( $request );
    140237        $this->assertErrorResponse( 'rest_forbidden', $response, 401 );
    141238    }
    142239
    143     public function test_get_non_public_taxonomy_no_permission() {
     240    /**
     241     * @dataProvider data_readable_http_methods
     242     * @ticket 56481
     243     *
     244     * @param string $method HTTP method to use.
     245     */
     246    public function test_get_non_public_taxonomy_no_permission( $method ) {
    144247        wp_set_current_user( self::$contributor_id );
    145248        register_taxonomy( 'api-private', 'post', array( 'public' => false ) );
    146249
    147         $request  = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/api-private' );
     250        $request  = new WP_REST_Request( $method, '/wp/v2/taxonomies/api-private' );
    148251        $response = rest_get_server()->dispatch( $request );
    149252        $this->assertErrorResponse( 'rest_forbidden', $response, 403 );
Note: See TracChangeset for help on using the changeset viewer.