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

    r58097 r59899  
    229229
    230230    /**
    231      * @ticket 41683
    232      */
    233     public function test_get_items_no_permission() {
     231     * @dataProvider data_readable_http_methods
     232     * @ticket 41683
     233     * @ticket 56481
     234     *
     235     * @param string $method The HTTP method to use.
     236     */
     237    public function test_get_items_no_permission( $method ) {
    234238        wp_set_current_user( 0 );
    235         $request  = new WP_REST_Request( 'GET', '/wp/v2/widgets' );
     239        $request  = new WP_REST_Request( $method, '/wp/v2/widgets' );
    236240        $response = rest_get_server()->dispatch( $request );
    237241        $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 401 );
     242    }
     243
     244    /**
     245     * Data provider intended to provide HTTP method names for testing GET and HEAD requests.
     246     *
     247     * @return array
     248     */
     249    public static function data_readable_http_methods() {
     250        return array(
     251            'GET request'  => array( 'GET' ),
     252            'HEAD request' => array( 'HEAD' ),
     253        );
    238254    }
    239255
     
    333349
    334350    /**
    335      * @ticket 41683
    336      */
    337     public function test_get_items_wrong_permission_author() {
     351     * @dataProvider data_readable_http_methods
     352     * @ticket 41683
     353     * @ticket 56481
     354     *
     355     * @param string $method The HTTP method to use.
     356     */
     357    public function test_get_items_wrong_permission_author( $method ) {
    338358        wp_set_current_user( self::$author_id );
    339         $request  = new WP_REST_Request( 'GET', '/wp/v2/widgets' );
     359        $request  = new WP_REST_Request( $method, '/wp/v2/widgets' );
    340360        $response = rest_get_server()->dispatch( $request );
    341361        $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 403 );
     
    378398        $request  = new WP_REST_Request( 'GET', '/wp/v2/widgets' );
    379399        $response = rest_get_server()->dispatch( $request );
    380         $data     = $response->get_data();
    381         $data     = $this->remove_links( $data );
     400        remove_filter( 'pre_http_request', array( $this, 'mocked_rss_response' ) );
     401        $data = $response->get_data();
     402        $data = $this->remove_links( $data );
    382403        $this->assertSameSets(
    383404            array(
     
    405426
    406427        $wp_widget_factory->widgets['WP_Widget_RSS']->widget_options['show_instance_in_rest'] = true;
     428    }
     429
     430    /**
     431     * @dataProvider data_readable_http_methods
     432     * @ticket 56481
     433     *
     434     * @param string $method The HTTP method to use.
     435     */
     436    public function test_get_items_with_head_request_should_not_prepare_widget_data( $method ) {
     437        $block_content = '<!-- wp:paragraph --><p>Block test</p><!-- /wp:paragraph -->';
     438
     439        $this->setup_widget(
     440            'rss',
     441            1,
     442            array(
     443                'title' => 'RSS test',
     444                'url'   => 'https://wordpress.org/news/feed',
     445            )
     446        );
     447        $this->setup_widget(
     448            'block',
     449            1,
     450            array(
     451                'content' => $block_content,
     452            )
     453        );
     454        $this->setup_sidebar(
     455            'sidebar-1',
     456            array(
     457                'name' => 'Test sidebar',
     458            ),
     459            array( 'block-1', 'rss-1', 'testwidget' )
     460        );
     461
     462        $request = new WP_REST_Request( 'HEAD', '/wp/v2/widgets' );
     463
     464        $hook_name = 'rest_prepare_post';
     465        $filter    = new MockAction();
     466        $callback  = array( $filter, 'filter' );
     467
     468        add_filter( $hook_name, $callback );
     469        $response = rest_get_server()->dispatch( $request );
     470        remove_filter( $hook_name, $callback );
     471
     472        $this->assertNotWPError( $response );
     473        $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' );
     474        $this->assertSame( 0, $filter->get_call_count(), 'The "' . $hook_name . '" filter was called when it should not be for HEAD requests.' );
     475        $this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
    407476    }
    408477
     
    529598
    530599    /**
    531      * @ticket 41683
    532      */
    533     public function test_get_item_no_permission() {
     600     * @dataProvider data_readable_http_methods
     601     * @ticket 56481
     602     *
     603     * @param string $method The HTTP method to use.
     604     */
     605    public function test_get_item_should_allow_adding_headers_via_filter( $method ) {
     606        $this->setup_widget(
     607            'text',
     608            1,
     609            array(
     610                'text' => 'Custom text test',
     611            )
     612        );
     613        $this->setup_sidebar(
     614            'sidebar-1',
     615            array(
     616                'name' => 'Test sidebar',
     617            ),
     618            array( 'text-1' )
     619        );
     620
     621        $request = new WP_REST_Request( $method, '/wp/v2/widgets/text-1' );
     622
     623        $hook_name = 'rest_prepare_widget';
     624        $filter    = new MockAction();
     625        $callback  = array( $filter, 'filter' );
     626        add_filter( $hook_name, $callback );
     627        $header_filter = new class() {
     628            public static function add_custom_header( $response ) {
     629                $response->header( 'X-Test-Header', 'Test' );
     630
     631                return $response;
     632            }
     633        };
     634        add_filter( $hook_name, array( $header_filter, 'add_custom_header' ) );
     635        $response = rest_get_server()->dispatch( $request );
     636        remove_filter( $hook_name, $callback );
     637        remove_filter( $hook_name, array( $header_filter, 'add_custom_header' ) );
     638
     639        $this->assertSame( 200, $response->get_status(), 'The response status should be 200.' );
     640        $this->assertSame( 1, $filter->get_call_count(), 'The "' . $hook_name . '" filter was not called when it should be for GET/HEAD requests.' );
     641        $headers = $response->get_headers();
     642        $this->assertArrayHasKey( 'X-Test-Header', $headers, 'The "X-Test-Header" header should be present in the response.' );
     643        $this->assertSame( 'Test', $headers['X-Test-Header'], 'The "X-Test-Header" header value should be equal to "Test".' );
     644        if ( 'HEAD' !== $method ) {
     645            return null;
     646        }
     647        $this->assertNull( $response->get_data(), 'The server should not generate a body in response to a HEAD request.' );
     648    }
     649
     650    /**
     651     * @dataProvider data_readable_http_methods
     652     * @ticket 41683
     653     * @ticket 56481
     654     *
     655     * @param string $method The HTTP method to use.
     656     */
     657    public function test_get_item_no_permission( $method ) {
    534658        wp_set_current_user( 0 );
    535659
     
    549673        );
    550674
    551         $request  = new WP_REST_Request( 'GET', '/wp/v2/widgets/text-1' );
     675        $request  = new WP_REST_Request( $method, '/wp/v2/widgets/text-1' );
    552676        $response = rest_get_server()->dispatch( $request );
    553677        $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 401 );
     
    555679
    556680    /**
    557      * @ticket 41683
    558      */
    559     public function test_get_item_wrong_permission_author() {
     681     * @dataProvider data_readable_http_methods
     682     * @ticket 41683
     683     * @ticket 56481
     684     *
     685     * @param string $method The HTTP method to use.
     686     */
     687    public function test_get_item_wrong_permission_author( $method ) {
    560688        wp_set_current_user( self::$author_id );
    561689        $this->setup_widget(
     
    573701        );
    574702
    575         $request  = new WP_REST_Request( 'GET', '/wp/v2/widgets/text-1' );
     703        $request  = new WP_REST_Request( $method, '/wp/v2/widgets/text-1' );
    576704        $response = rest_get_server()->dispatch( $request );
    577705        $this->assertErrorResponse( 'rest_cannot_manage_widgets', $response, 403 );
Note: See TracChangeset for help on using the changeset viewer.