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

    r56745 r59899  
    180180    }
    181181
    182     public function test_get_items_no_permission() {
     182    /**
     183     * @ticket 56481
     184     */
     185    public function test_get_items_with_head_request_should_not_prepare_autosaves_data() {
     186        $request = new WP_REST_Request( 'HEAD', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     187
     188        $hook_name = 'rest_prepare_autosave';
     189        $filter    = new MockAction();
     190        $callback  = array( $filter, 'filter' );
     191
     192        add_filter( $hook_name, $callback );
     193        $response = rest_get_server()->dispatch( $request );
     194        remove_filter( $hook_name, $callback );
     195
     196        $this->assertNotWPError( $response );
     197        $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' );
     198        $this->assertSame( 0, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' );
     199        $this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
     200    }
     201
     202    /**
     203     * @dataProvider data_readable_http_methods
     204     * @ticket 56481
     205     *
     206     * @param string $method The HTTP method to use.
     207     */
     208    public function test_get_items_no_permission( $method ) {
    183209        wp_set_current_user( 0 );
    184         $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     210        $request  = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$post_id . '/autosaves' );
    185211        $response = rest_get_server()->dispatch( $request );
    186212        $this->assertErrorResponse( 'rest_cannot_read', $response, 401 );
     
    190216    }
    191217
    192     public function test_get_items_missing_parent() {
    193         wp_set_current_user( self::$editor_id );
    194         $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/autosaves' );
     218    /**
     219     * Data provider intended to provide HTTP method names for testing GET and HEAD requests.
     220     *
     221     * @return array
     222     */
     223    public static function data_readable_http_methods() {
     224        return array(
     225            'GET request'  => array( 'GET' ),
     226            'HEAD request' => array( 'HEAD' ),
     227        );
     228    }
     229
     230    /**
     231     * @dataProvider data_readable_http_methods
     232     * @ticket 56481
     233     *
     234     * @param string $method The HTTP method to use.
     235     */
     236    public function test_get_items_missing_parent( $method ) {
     237        wp_set_current_user( self::$editor_id );
     238        $request  = new WP_REST_Request( $method, '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/autosaves' );
    195239        $response = rest_get_server()->dispatch( $request );
    196240        $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
    197241    }
    198242
    199     public function test_get_items_invalid_parent_post_type() {
    200         wp_set_current_user( self::$editor_id );
    201         $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$page_id . '/autosaves' );
     243    /**
     244     * @dataProvider data_readable_http_methods
     245     * @ticket 56481
     246     *
     247     * @param string $method The HTTP method to use.
     248     */
     249    public function test_get_items_invalid_parent_post_type( $method ) {
     250        wp_set_current_user( self::$editor_id );
     251        $request  = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$page_id . '/autosaves' );
    202252        $response = rest_get_server()->dispatch( $request );
    203253        $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
     
    231281    }
    232282
     283    /**
     284     * @dataProvider data_readable_http_methods
     285     * @ticket 56481
     286     *
     287     * @param string $method The HTTP method to use.
     288     */
     289    public function test_get_item_should_allow_adding_headers_via_filter( $method ) {
     290        wp_set_current_user( self::$editor_id );
     291        $request = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     292
     293        $hook_name = 'rest_prepare_autosave';
     294        $filter    = new MockAction();
     295        $callback  = array( $filter, 'filter' );
     296        add_filter( $hook_name, $callback );
     297        $header_filter = new class() {
     298            public static function add_custom_header( $response ) {
     299                $response->header( 'X-Test-Header', 'Test' );
     300
     301                return $response;
     302            }
     303        };
     304        add_filter( $hook_name, array( $header_filter, 'add_custom_header' ) );
     305        $response = rest_get_server()->dispatch( $request );
     306        remove_filter( $hook_name, $callback );
     307        remove_filter( $hook_name, array( $header_filter, 'add_custom_header' ) );
     308
     309        $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' );
     310        $this->assertSame( 1, $filter->get_call_count(), 'The "' . $hook_name . '" filter was not called when it should be for GET/HEAD requests.' );
     311        $headers = $response->get_headers();
     312        $this->assertArrayHasKey( 'X-Test-Header', $headers, 'The "X-Test-Header" header should be present in the response.' );
     313        $this->assertSame( 'Test', $headers['X-Test-Header'], 'The "X-Test-Header" header value should be equal to "Test".' );
     314        if ( 'HEAD' !== $method ) {
     315            return null;
     316        }
     317        $this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
     318    }
     319
    233320    public function test_get_item_embed_context() {
    234321        wp_set_current_user( self::$editor_id );
     
    249336    }
    250337
    251     public function test_get_item_no_permission() {
    252         $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     338    /**
     339     * @dataProvider data_readable_http_methods
     340     * @ticket 56481
     341     *
     342     * @param string $method The HTTP method to use.
     343     */
     344    public function test_get_item_no_permission( $method ) {
     345        $request = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
    253346        wp_set_current_user( self::$contributor_id );
    254347        $response = rest_get_server()->dispatch( $request );
     
    256349    }
    257350
    258     public function test_get_item_missing_parent() {
    259         wp_set_current_user( self::$editor_id );
    260         $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/autosaves/' . self::$autosave_post_id );
     351    /**
     352     * @dataProvider data_readable_http_methods
     353     * @ticket 56481
     354     *
     355     * @param string $method The HTTP method to use.
     356     */
     357    public function test_get_item_missing_parent( $method ) {
     358        wp_set_current_user( self::$editor_id );
     359        $request  = new WP_REST_Request( $method, '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/autosaves/' . self::$autosave_post_id );
    261360        $response = rest_get_server()->dispatch( $request );
    262361        $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
    263362    }
    264363
    265     public function test_get_item_invalid_parent_post_type() {
    266         wp_set_current_user( self::$editor_id );
    267         $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$page_id . '/autosaves' );
     364    /**
     365     * @dataProvider data_readable_http_methods
     366     * @ticket 56481
     367     *
     368     * @param string $method The HTTP method to use.
     369     */
     370    public function test_get_item_invalid_parent_post_type( $method ) {
     371        wp_set_current_user( self::$editor_id );
     372        $request  = new WP_REST_Request( $method, '/wp/v2/posts/' . self::$page_id . '/autosaves' );
    268373        $response = rest_get_server()->dispatch( $request );
    269374        $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
Note: See TracChangeset for help on using the changeset viewer.